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 editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod split_editor_view;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction_types::Direction;
56pub use editor_settings::{
57 CompletionDetailAlignment, CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings,
58 HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
59};
60pub use element::{
61 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, OverlayPainter,
62 OverlayPainterData, PointForPosition, render_breadcrumb_text,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use inlays::Inlay;
67pub use items::MAX_TAB_TITLE_LEN;
68pub use lsp::CompletionContext;
69pub use lsp_ext::lsp_tasks;
70pub use multi_buffer::{
71 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
72 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
73 ToPoint,
74};
75pub use split::{SplitDiffFeatureFlag, SplittableEditor, ToggleLockedCursors, ToggleSplitDiff};
76pub use split_editor_view::SplitEditorView;
77pub use text::Bias;
78
79use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
80use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
81use anyhow::{Context as _, Result, anyhow, bail};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::ReplicaId;
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction_types::{
95 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
96 SuggestionDisplayType,
97};
98use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
99use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
100use futures::{
101 FutureExt,
102 future::{self, Shared, join},
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
112 Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled, Subscription, Task,
113 TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
114 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
115 pulsating_between, px, relative, size,
116};
117use hover_links::{HoverLink, HoveredLinkState, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
125 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
126 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
127 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
128 TreeSitterOptions, WordsQuery,
129 language_settings::{
130 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
131 all_language_settings, language_settings,
132 },
133 point_from_lsp, point_to_lsp, text_diff_with_options,
134};
135use linked_editing_ranges::refresh_linked_ranges;
136use lsp::{
137 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
138 LanguageServerId,
139};
140use lsp_colors::LspColorData;
141use markdown::Markdown;
142use mouse_context_menu::MouseContextMenu;
143use movement::TextLayoutDetails;
144use multi_buffer::{
145 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
152 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
153 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::GitStoreEvent,
162 lsp_store::{
163 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
164 OpenLspBufferHandle,
165 },
166 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
167};
168use rand::seq::SliceRandom;
169use regex::Regex;
170use rpc::{ErrorCode, ErrorExt, proto::PeerId};
171use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
172use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
173use serde::{Deserialize, Serialize};
174use settings::{
175 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
176 update_settings_file,
177};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::{
181 any::{Any, TypeId},
182 borrow::Cow,
183 cell::{OnceCell, RefCell},
184 cmp::{self, Ordering, Reverse},
185 collections::hash_map,
186 iter::{self, Peekable},
187 mem,
188 num::NonZeroU32,
189 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
190 path::{Path, PathBuf},
191 rc::Rc,
192 sync::Arc,
193 time::{Duration, Instant},
194};
195use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
196use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
197use theme::{
198 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
199 ThemeSettings, observe_buffer_font_size_adjustment,
200};
201use ui::{
202 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
203 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
204};
205use ui_input::ErasedEditor;
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
209 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
210 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::{CollapseDirection, SearchEvent},
214};
215use zed_actions::editor::{MoveDown, MoveUp};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 editor_settings::MultiCursorModifier,
220 hover_links::{find_url, find_url_from_range},
221 inlays::{
222 InlineValueCache,
223 inlay_hints::{LspInlayHintData, inlay_hint_settings},
224 },
225 scroll::{ScrollOffset, ScrollPixelOffset},
226 selections_collection::resolve_selections_wrapping_blocks,
227 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
228};
229
230pub const FILE_HEADER_HEIGHT: u32 = 2;
231pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
232const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
233const MAX_LINE_LEN: usize = 1024;
234const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
235const MAX_SELECTION_HISTORY_LEN: usize = 1024;
236pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
237#[doc(hidden)]
238pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
239pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
240
241pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
242pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
243pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
244pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
245
246pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
247pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
248pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
249
250pub type RenderDiffHunkControlsFn = Arc<
251 dyn Fn(
252 u32,
253 &DiffHunkStatus,
254 Range<Anchor>,
255 bool,
256 Pixels,
257 &Entity<Editor>,
258 &mut Window,
259 &mut App,
260 ) -> AnyElement,
261>;
262
263enum ReportEditorEvent {
264 Saved { auto_saved: bool },
265 EditorOpened,
266 Closed,
267}
268
269impl ReportEditorEvent {
270 pub fn event_type(&self) -> &'static str {
271 match self {
272 Self::Saved { .. } => "Editor Saved",
273 Self::EditorOpened => "Editor Opened",
274 Self::Closed => "Editor Closed",
275 }
276 }
277}
278
279pub enum ActiveDebugLine {}
280pub enum DebugStackFrameLine {}
281enum DocumentHighlightRead {}
282enum DocumentHighlightWrite {}
283enum InputComposition {}
284pub enum PendingInput {}
285enum SelectedTextHighlight {}
286
287pub enum ConflictsOuter {}
288pub enum ConflictsOurs {}
289pub enum ConflictsTheirs {}
290pub enum ConflictsOursMarker {}
291pub enum ConflictsTheirsMarker {}
292
293pub struct HunkAddedColor;
294pub struct HunkRemovedColor;
295
296#[derive(Debug, Copy, Clone, PartialEq, Eq)]
297pub enum Navigated {
298 Yes,
299 No,
300}
301
302impl Navigated {
303 pub fn from_bool(yes: bool) -> Navigated {
304 if yes { Navigated::Yes } else { Navigated::No }
305 }
306}
307
308#[derive(Debug, Clone, PartialEq, Eq)]
309enum DisplayDiffHunk {
310 Folded {
311 display_row: DisplayRow,
312 },
313 Unfolded {
314 is_created_file: bool,
315 diff_base_byte_range: Range<usize>,
316 display_row_range: Range<DisplayRow>,
317 multi_buffer_range: Range<Anchor>,
318 status: DiffHunkStatus,
319 word_diffs: Vec<Range<MultiBufferOffset>>,
320 },
321}
322
323pub enum HideMouseCursorOrigin {
324 TypingAction,
325 MovementAction,
326}
327
328pub fn init(cx: &mut App) {
329 cx.set_global(GlobalBlameRenderer(Arc::new(())));
330
331 workspace::register_project_item::<Editor>(cx);
332 workspace::FollowableViewRegistry::register::<Editor>(cx);
333 workspace::register_serializable_item::<Editor>(cx);
334
335 cx.observe_new(
336 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
337 workspace.register_action(Editor::new_file);
338 workspace.register_action(Editor::new_file_split);
339 workspace.register_action(Editor::new_file_vertical);
340 workspace.register_action(Editor::new_file_horizontal);
341 workspace.register_action(Editor::cancel_language_server_work);
342 workspace.register_action(Editor::toggle_focus);
343 },
344 )
345 .detach();
346
347 cx.on_action(move |_: &workspace::NewFile, cx| {
348 let app_state = workspace::AppState::global(cx);
349 if let Some(app_state) = app_state.upgrade() {
350 workspace::open_new(
351 Default::default(),
352 app_state,
353 cx,
354 |workspace, window, cx| {
355 Editor::new_file(workspace, &Default::default(), window, cx)
356 },
357 )
358 .detach();
359 }
360 })
361 .on_action(move |_: &workspace::NewWindow, cx| {
362 let app_state = workspace::AppState::global(cx);
363 if let Some(app_state) = app_state.upgrade() {
364 workspace::open_new(
365 Default::default(),
366 app_state,
367 cx,
368 |workspace, window, cx| {
369 cx.activate(true);
370 Editor::new_file(workspace, &Default::default(), window, cx)
371 },
372 )
373 .detach();
374 }
375 });
376 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
377 Arc::new(ErasedEditorImpl(
378 cx.new(|cx| Editor::single_line(window, cx)),
379 )) as Arc<dyn ErasedEditor>
380 });
381 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
382}
383
384pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
385 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
386}
387
388pub trait DiagnosticRenderer {
389 fn render_group(
390 &self,
391 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
392 buffer_id: BufferId,
393 snapshot: EditorSnapshot,
394 editor: WeakEntity<Editor>,
395 language_registry: Option<Arc<LanguageRegistry>>,
396 cx: &mut App,
397 ) -> Vec<BlockProperties<Anchor>>;
398
399 fn render_hover(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
402 range: Range<Point>,
403 buffer_id: BufferId,
404 language_registry: Option<Arc<LanguageRegistry>>,
405 cx: &mut App,
406 ) -> Option<Entity<markdown::Markdown>>;
407
408 fn open_link(
409 &self,
410 editor: &mut Editor,
411 link: SharedString,
412 window: &mut Window,
413 cx: &mut Context<Editor>,
414 );
415}
416
417pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
418
419impl GlobalDiagnosticRenderer {
420 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
421 cx.try_global::<Self>().map(|g| g.0.clone())
422 }
423}
424
425impl gpui::Global for GlobalDiagnosticRenderer {}
426pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
427 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
428}
429
430pub struct SearchWithinRange;
431
432trait InvalidationRegion {
433 fn ranges(&self) -> &[Range<Anchor>];
434}
435
436#[derive(Clone, Debug, PartialEq)]
437pub enum SelectPhase {
438 Begin {
439 position: DisplayPoint,
440 add: bool,
441 click_count: usize,
442 },
443 BeginColumnar {
444 position: DisplayPoint,
445 reset: bool,
446 mode: ColumnarMode,
447 goal_column: u32,
448 },
449 Extend {
450 position: DisplayPoint,
451 click_count: usize,
452 },
453 Update {
454 position: DisplayPoint,
455 goal_column: u32,
456 scroll_delta: gpui::Point<f32>,
457 },
458 End,
459}
460
461#[derive(Clone, Debug, PartialEq)]
462pub enum ColumnarMode {
463 FromMouse,
464 FromSelection,
465}
466
467#[derive(Clone, Debug)]
468pub enum SelectMode {
469 Character,
470 Word(Range<Anchor>),
471 Line(Range<Anchor>),
472 All,
473}
474
475#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
476pub enum SizingBehavior {
477 /// The editor will layout itself using `size_full` and will include the vertical
478 /// scroll margin as requested by user settings.
479 #[default]
480 Default,
481 /// The editor will layout itself using `size_full`, but will not have any
482 /// vertical overscroll.
483 ExcludeOverscrollMargin,
484 /// The editor will request a vertical size according to its content and will be
485 /// layouted without a vertical scroll margin.
486 SizeByContent,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// Determines the sizing behavior for this editor
502 sizing_behavior: SizingBehavior,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sizing_behavior: SizingBehavior::Default,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, Arc<str>)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 /// Move to a specific location in the active editor
642 MoveWithin {
643 target: Anchor,
644 snapshot: BufferSnapshot,
645 },
646 /// Move to a specific location in a different editor (not the active one)
647 MoveOutside {
648 target: language::Anchor,
649 snapshot: BufferSnapshot,
650 },
651}
652
653struct EditPredictionState {
654 inlay_ids: Vec<InlayId>,
655 completion: EditPrediction,
656 completion_id: Option<SharedString>,
657 invalidation_range: Option<Range<Anchor>>,
658}
659
660enum EditPredictionSettings {
661 Disabled,
662 Enabled {
663 show_in_menu: bool,
664 preview_requires_modifier: bool,
665 },
666}
667
668enum EditPredictionHighlight {}
669
670#[derive(Debug, Clone)]
671struct InlineDiagnostic {
672 message: SharedString,
673 group_id: usize,
674 is_primary: bool,
675 start: Point,
676 severity: lsp::DiagnosticSeverity,
677}
678
679pub enum MenuEditPredictionsPolicy {
680 Never,
681 ByProvider,
682}
683
684pub enum EditPredictionPreview {
685 /// Modifier is not pressed
686 Inactive { released_too_fast: bool },
687 /// Modifier pressed
688 Active {
689 since: Instant,
690 previous_scroll_position: Option<ScrollAnchor>,
691 },
692}
693
694impl EditPredictionPreview {
695 pub fn released_too_fast(&self) -> bool {
696 match self {
697 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
698 EditPredictionPreview::Active { .. } => false,
699 }
700 }
701
702 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
703 if let EditPredictionPreview::Active {
704 previous_scroll_position,
705 ..
706 } = self
707 {
708 *previous_scroll_position = scroll_position;
709 }
710 }
711}
712
713pub struct ContextMenuOptions {
714 pub min_entries_visible: usize,
715 pub max_entries_visible: usize,
716 pub placement: Option<ContextMenuPlacement>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ContextMenuPlacement {
721 Above,
722 Below,
723}
724
725#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
726struct EditorActionId(usize);
727
728impl EditorActionId {
729 pub fn post_inc(&mut self) -> Self {
730 let answer = self.0;
731
732 *self = Self(answer + 1);
733
734 Self(answer)
735 }
736}
737
738// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
739// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
740
741type BackgroundHighlight = (
742 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
743 Arc<[Range<Anchor>]>,
744);
745type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
746
747#[derive(Default)]
748struct ScrollbarMarkerState {
749 scrollbar_size: Size<Pixels>,
750 dirty: bool,
751 markers: Arc<[PaintQuad]>,
752 pending_refresh: Option<Task<Result<()>>>,
753}
754
755impl ScrollbarMarkerState {
756 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
757 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
758 }
759}
760
761#[derive(Clone, Copy, PartialEq, Eq)]
762pub enum MinimapVisibility {
763 Disabled,
764 Enabled {
765 /// The configuration currently present in the users settings.
766 setting_configuration: bool,
767 /// Whether to override the currently set visibility from the users setting.
768 toggle_override: bool,
769 },
770}
771
772impl MinimapVisibility {
773 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
774 if mode.is_full() {
775 Self::Enabled {
776 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
777 toggle_override: false,
778 }
779 } else {
780 Self::Disabled
781 }
782 }
783
784 fn hidden(&self) -> Self {
785 match *self {
786 Self::Enabled {
787 setting_configuration,
788 ..
789 } => Self::Enabled {
790 setting_configuration,
791 toggle_override: setting_configuration,
792 },
793 Self::Disabled => Self::Disabled,
794 }
795 }
796
797 fn disabled(&self) -> bool {
798 matches!(*self, Self::Disabled)
799 }
800
801 fn settings_visibility(&self) -> bool {
802 match *self {
803 Self::Enabled {
804 setting_configuration,
805 ..
806 } => setting_configuration,
807 _ => false,
808 }
809 }
810
811 fn visible(&self) -> bool {
812 match *self {
813 Self::Enabled {
814 setting_configuration,
815 toggle_override,
816 } => setting_configuration ^ toggle_override,
817 _ => false,
818 }
819 }
820
821 fn toggle_visibility(&self) -> Self {
822 match *self {
823 Self::Enabled {
824 toggle_override,
825 setting_configuration,
826 } => Self::Enabled {
827 setting_configuration,
828 toggle_override: !toggle_override,
829 },
830 Self::Disabled => Self::Disabled,
831 }
832 }
833}
834
835#[derive(Debug, Clone, Copy, PartialEq, Eq)]
836pub enum BufferSerialization {
837 All,
838 NonDirtyBuffers,
839}
840
841impl BufferSerialization {
842 fn new(restore_unsaved_buffers: bool) -> Self {
843 if restore_unsaved_buffers {
844 Self::All
845 } else {
846 Self::NonDirtyBuffers
847 }
848 }
849}
850
851#[derive(Clone, Debug)]
852struct RunnableTasks {
853 templates: Vec<(TaskSourceKind, TaskTemplate)>,
854 offset: multi_buffer::Anchor,
855 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
856 column: u32,
857 // Values of all named captures, including those starting with '_'
858 extra_variables: HashMap<String, String>,
859 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
860 context_range: Range<BufferOffset>,
861}
862
863impl RunnableTasks {
864 fn resolve<'a>(
865 &'a self,
866 cx: &'a task::TaskContext,
867 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
868 self.templates.iter().filter_map(|(kind, template)| {
869 template
870 .resolve_task(&kind.to_id_base(), cx)
871 .map(|task| (kind.clone(), task))
872 })
873 }
874}
875
876#[derive(Clone)]
877pub struct ResolvedTasks {
878 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
879 position: Anchor,
880}
881
882/// Addons allow storing per-editor state in other crates (e.g. Vim)
883pub trait Addon: 'static {
884 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
885
886 fn render_buffer_header_controls(
887 &self,
888 _: &ExcerptInfo,
889 _: &Window,
890 _: &App,
891 ) -> Option<AnyElement> {
892 None
893 }
894
895 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
896 None
897 }
898
899 fn to_any(&self) -> &dyn std::any::Any;
900
901 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
902 None
903 }
904}
905
906struct ChangeLocation {
907 current: Option<Vec<Anchor>>,
908 original: Vec<Anchor>,
909}
910impl ChangeLocation {
911 fn locations(&self) -> &[Anchor] {
912 self.current.as_ref().unwrap_or(&self.original)
913 }
914}
915
916/// A set of caret positions, registered when the editor was edited.
917pub struct ChangeList {
918 changes: Vec<ChangeLocation>,
919 /// Currently "selected" change.
920 position: Option<usize>,
921}
922
923impl ChangeList {
924 pub fn new() -> Self {
925 Self {
926 changes: Vec::new(),
927 position: None,
928 }
929 }
930
931 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
932 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
933 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
934 if self.changes.is_empty() {
935 return None;
936 }
937
938 let prev = self.position.unwrap_or(self.changes.len());
939 let next = if direction == Direction::Prev {
940 prev.saturating_sub(count)
941 } else {
942 (prev + count).min(self.changes.len() - 1)
943 };
944 self.position = Some(next);
945 self.changes.get(next).map(|change| change.locations())
946 }
947
948 /// Adds a new change to the list, resetting the change list position.
949 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
950 self.position.take();
951 if let Some(last) = self.changes.last_mut()
952 && group
953 {
954 last.current = Some(new_positions)
955 } else {
956 self.changes.push(ChangeLocation {
957 original: new_positions,
958 current: None,
959 });
960 }
961 }
962
963 pub fn last(&self) -> Option<&[Anchor]> {
964 self.changes.last().map(|change| change.locations())
965 }
966
967 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
968 self.changes.last().map(|change| change.original.as_slice())
969 }
970
971 pub fn invert_last_group(&mut self) {
972 if let Some(last) = self.changes.last_mut()
973 && let Some(current) = last.current.as_mut()
974 {
975 mem::swap(&mut last.original, current);
976 }
977 }
978}
979
980#[derive(Clone)]
981struct InlineBlamePopoverState {
982 scroll_handle: ScrollHandle,
983 commit_message: Option<ParsedCommitMessage>,
984 markdown: Entity<Markdown>,
985}
986
987struct InlineBlamePopover {
988 position: gpui::Point<Pixels>,
989 hide_task: Option<Task<()>>,
990 popover_bounds: Option<Bounds<Pixels>>,
991 popover_state: InlineBlamePopoverState,
992 keyboard_grace: bool,
993}
994
995enum SelectionDragState {
996 /// State when no drag related activity is detected.
997 None,
998 /// State when the mouse is down on a selection that is about to be dragged.
999 ReadyToDrag {
1000 selection: Selection<Anchor>,
1001 click_position: gpui::Point<Pixels>,
1002 mouse_down_time: Instant,
1003 },
1004 /// State when the mouse is dragging the selection in the editor.
1005 Dragging {
1006 selection: Selection<Anchor>,
1007 drop_cursor: Selection<Anchor>,
1008 hide_drop_cursor: bool,
1009 },
1010}
1011
1012enum ColumnarSelectionState {
1013 FromMouse {
1014 selection_tail: Anchor,
1015 display_point: Option<DisplayPoint>,
1016 },
1017 FromSelection {
1018 selection_tail: Anchor,
1019 },
1020}
1021
1022/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1023/// a breakpoint on them.
1024#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1025struct PhantomBreakpointIndicator {
1026 display_row: DisplayRow,
1027 /// There's a small debounce between hovering over the line and showing the indicator.
1028 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1029 is_active: bool,
1030 collides_with_existing_breakpoint: bool,
1031}
1032
1033/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1034/// in diff view mode.
1035#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1036pub(crate) struct PhantomDiffReviewIndicator {
1037 /// The starting anchor of the selection (or the only row if not dragging).
1038 pub start: Anchor,
1039 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1040 pub end: Anchor,
1041 /// There's a small debounce between hovering over the line and showing the indicator.
1042 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1043 pub is_active: bool,
1044}
1045
1046#[derive(Clone, Debug)]
1047pub(crate) struct DiffReviewDragState {
1048 pub start_anchor: Anchor,
1049 pub current_anchor: Anchor,
1050}
1051
1052impl DiffReviewDragState {
1053 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1054 let start = self.start_anchor.to_display_point(snapshot).row();
1055 let current = self.current_anchor.to_display_point(snapshot).row();
1056
1057 (start..=current).sorted()
1058 }
1059}
1060
1061/// Identifies a specific hunk in the diff buffer.
1062/// Used as a key to group comments by their location.
1063#[derive(Clone, Debug)]
1064pub struct DiffHunkKey {
1065 /// The file path (relative to worktree) this hunk belongs to.
1066 pub file_path: Arc<util::rel_path::RelPath>,
1067 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1068 pub hunk_start_anchor: Anchor,
1069}
1070
1071/// A review comment stored locally before being sent to the Agent panel.
1072#[derive(Clone)]
1073pub struct StoredReviewComment {
1074 /// Unique identifier for this comment (for edit/delete operations).
1075 pub id: usize,
1076 /// The comment text entered by the user.
1077 pub comment: String,
1078 /// Anchors for the code range being reviewed.
1079 pub range: Range<Anchor>,
1080 /// Timestamp when the comment was created (for chronological ordering).
1081 pub created_at: Instant,
1082 /// Whether this comment is currently being edited inline.
1083 pub is_editing: bool,
1084}
1085
1086impl StoredReviewComment {
1087 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1088 Self {
1089 id,
1090 comment,
1091 range: anchor_range,
1092 created_at: Instant::now(),
1093 is_editing: false,
1094 }
1095 }
1096}
1097
1098/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1099pub(crate) struct DiffReviewOverlay {
1100 pub anchor_range: Range<Anchor>,
1101 /// The block ID for the overlay.
1102 pub block_id: CustomBlockId,
1103 /// The editor entity for the review input.
1104 pub prompt_editor: Entity<Editor>,
1105 /// The hunk key this overlay belongs to.
1106 pub hunk_key: DiffHunkKey,
1107 /// Whether the comments section is expanded.
1108 pub comments_expanded: bool,
1109 /// Editors for comments currently being edited inline.
1110 /// Key: comment ID, Value: Editor entity for inline editing.
1111 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1112 /// Subscriptions for inline edit editors' action handlers.
1113 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1114 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1115 /// The current user's avatar URI for display in comment rows.
1116 pub user_avatar_uri: Option<SharedUri>,
1117 /// Subscription to keep the action handler alive.
1118 _subscription: Subscription,
1119}
1120
1121/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1122///
1123/// See the [module level documentation](self) for more information.
1124pub struct Editor {
1125 focus_handle: FocusHandle,
1126 last_focused_descendant: Option<WeakFocusHandle>,
1127 /// The text buffer being edited
1128 buffer: Entity<MultiBuffer>,
1129 /// Map of how text in the buffer should be displayed.
1130 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1131 pub display_map: Entity<DisplayMap>,
1132 placeholder_display_map: Option<Entity<DisplayMap>>,
1133 pub selections: SelectionsCollection,
1134 pub scroll_manager: ScrollManager,
1135 /// When inline assist editors are linked, they all render cursors because
1136 /// typing enters text into each of them, even the ones that aren't focused.
1137 pub(crate) show_cursor_when_unfocused: bool,
1138 columnar_selection_state: Option<ColumnarSelectionState>,
1139 add_selections_state: Option<AddSelectionsState>,
1140 select_next_state: Option<SelectNextState>,
1141 select_prev_state: Option<SelectNextState>,
1142 selection_history: SelectionHistory,
1143 defer_selection_effects: bool,
1144 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1145 autoclose_regions: Vec<AutocloseRegion>,
1146 snippet_stack: InvalidationStack<SnippetState>,
1147 select_syntax_node_history: SelectSyntaxNodeHistory,
1148 ime_transaction: Option<TransactionId>,
1149 pub diagnostics_max_severity: DiagnosticSeverity,
1150 active_diagnostics: ActiveDiagnostic,
1151 show_inline_diagnostics: bool,
1152 inline_diagnostics_update: Task<()>,
1153 inline_diagnostics_enabled: bool,
1154 diagnostics_enabled: bool,
1155 word_completions_enabled: bool,
1156 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1157 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1158 hard_wrap: Option<usize>,
1159 project: Option<Entity<Project>>,
1160 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1161 completion_provider: Option<Rc<dyn CompletionProvider>>,
1162 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1163 blink_manager: Entity<BlinkManager>,
1164 show_cursor_names: bool,
1165 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1166 pub show_local_selections: bool,
1167 mode: EditorMode,
1168 show_breadcrumbs: bool,
1169 show_gutter: bool,
1170 show_scrollbars: ScrollbarAxes,
1171 minimap_visibility: MinimapVisibility,
1172 offset_content: bool,
1173 disable_expand_excerpt_buttons: bool,
1174 delegate_expand_excerpts: bool,
1175 show_line_numbers: Option<bool>,
1176 use_relative_line_numbers: Option<bool>,
1177 show_git_diff_gutter: Option<bool>,
1178 show_code_actions: Option<bool>,
1179 show_runnables: Option<bool>,
1180 show_breakpoints: Option<bool>,
1181 show_diff_review_button: bool,
1182 show_wrap_guides: Option<bool>,
1183 show_indent_guides: Option<bool>,
1184 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1185 highlight_order: usize,
1186 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1187 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1188 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1189 scrollbar_marker_state: ScrollbarMarkerState,
1190 active_indent_guides_state: ActiveIndentGuidesState,
1191 nav_history: Option<ItemNavHistory>,
1192 context_menu: RefCell<Option<CodeContextMenu>>,
1193 context_menu_options: Option<ContextMenuOptions>,
1194 mouse_context_menu: Option<MouseContextMenu>,
1195 completion_tasks: Vec<(CompletionId, Task<()>)>,
1196 inline_blame_popover: Option<InlineBlamePopover>,
1197 inline_blame_popover_show_task: Option<Task<()>>,
1198 signature_help_state: SignatureHelpState,
1199 auto_signature_help: Option<bool>,
1200 find_all_references_task_sources: Vec<Anchor>,
1201 next_completion_id: CompletionId,
1202 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1203 code_actions_task: Option<Task<Result<()>>>,
1204 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1205 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1206 debounced_selection_highlight_complete: bool,
1207 document_highlights_task: Option<Task<()>>,
1208 linked_editing_range_task: Option<Task<Option<()>>>,
1209 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1210 pending_rename: Option<RenameState>,
1211 searchable: bool,
1212 cursor_shape: CursorShape,
1213 /// Whether the cursor is offset one character to the left when something is
1214 /// selected (needed for vim visual mode)
1215 cursor_offset_on_selection: bool,
1216 current_line_highlight: Option<CurrentLineHighlight>,
1217 /// Whether to collapse search match ranges to just their start position.
1218 /// When true, navigating to a match positions the cursor at the match
1219 /// without selecting the matched text.
1220 collapse_matches: bool,
1221 autoindent_mode: Option<AutoindentMode>,
1222 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1223 input_enabled: bool,
1224 use_modal_editing: bool,
1225 read_only: bool,
1226 leader_id: Option<CollaboratorId>,
1227 remote_id: Option<ViewId>,
1228 pub hover_state: HoverState,
1229 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1230 prev_pressure_stage: Option<PressureStage>,
1231 gutter_hovered: bool,
1232 hovered_link_state: Option<HoveredLinkState>,
1233 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1234 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1235 active_edit_prediction: Option<EditPredictionState>,
1236 /// Used to prevent flickering as the user types while the menu is open
1237 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1238 edit_prediction_settings: EditPredictionSettings,
1239 edit_predictions_hidden_for_vim_mode: bool,
1240 show_edit_predictions_override: Option<bool>,
1241 show_completions_on_input_override: Option<bool>,
1242 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1243 edit_prediction_preview: EditPredictionPreview,
1244 edit_prediction_indent_conflict: bool,
1245 edit_prediction_requires_modifier_in_indent_conflict: bool,
1246 next_inlay_id: usize,
1247 next_color_inlay_id: usize,
1248 _subscriptions: Vec<Subscription>,
1249 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1250 gutter_dimensions: GutterDimensions,
1251 style: Option<EditorStyle>,
1252 text_style_refinement: Option<TextStyleRefinement>,
1253 next_editor_action_id: EditorActionId,
1254 editor_actions: Rc<
1255 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1256 >,
1257 use_autoclose: bool,
1258 use_auto_surround: bool,
1259 auto_replace_emoji_shortcode: bool,
1260 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1261 show_git_blame_gutter: bool,
1262 show_git_blame_inline: bool,
1263 show_git_blame_inline_delay_task: Option<Task<()>>,
1264 git_blame_inline_enabled: bool,
1265 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1266 buffer_serialization: Option<BufferSerialization>,
1267 show_selection_menu: Option<bool>,
1268 blame: Option<Entity<GitBlame>>,
1269 blame_subscription: Option<Subscription>,
1270 custom_context_menu: Option<
1271 Box<
1272 dyn 'static
1273 + Fn(
1274 &mut Self,
1275 DisplayPoint,
1276 &mut Window,
1277 &mut Context<Self>,
1278 ) -> Option<Entity<ui::ContextMenu>>,
1279 >,
1280 >,
1281 last_bounds: Option<Bounds<Pixels>>,
1282 last_position_map: Option<Rc<PositionMap>>,
1283 expect_bounds_change: Option<Bounds<Pixels>>,
1284 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1285 tasks_update_task: Option<Task<()>>,
1286 breakpoint_store: Option<Entity<BreakpointStore>>,
1287 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1288 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1289 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1290 /// Active diff review overlays. Multiple overlays can be open simultaneously
1291 /// when hunks have comments stored.
1292 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1293 /// Stored review comments grouped by hunk.
1294 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1295 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1296 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1297 /// Counter for generating unique comment IDs.
1298 next_review_comment_id: usize,
1299 hovered_diff_hunk_row: Option<DisplayRow>,
1300 pull_diagnostics_task: Task<()>,
1301 in_project_search: bool,
1302 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1303 breadcrumb_header: Option<String>,
1304 focused_block: Option<FocusedBlock>,
1305 next_scroll_position: NextScrollCursorCenterTopBottom,
1306 addons: HashMap<TypeId, Box<dyn Addon>>,
1307 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1308 load_diff_task: Option<Shared<Task<()>>>,
1309 /// Whether we are temporarily displaying a diff other than git's
1310 temporary_diff_override: bool,
1311 selection_mark_mode: bool,
1312 toggle_fold_multiple_buffers: Task<()>,
1313 _scroll_cursor_center_top_bottom_task: Task<()>,
1314 serialize_selections: Task<()>,
1315 serialize_folds: Task<()>,
1316 mouse_cursor_hidden: bool,
1317 minimap: Option<Entity<Self>>,
1318 hide_mouse_mode: HideMouseMode,
1319 pub change_list: ChangeList,
1320 inline_value_cache: InlineValueCache,
1321 number_deleted_lines: bool,
1322
1323 selection_drag_state: SelectionDragState,
1324 colors: Option<LspColorData>,
1325 post_scroll_update: Task<()>,
1326 refresh_colors_task: Task<()>,
1327 inlay_hints: Option<LspInlayHintData>,
1328 folding_newlines: Task<()>,
1329 select_next_is_case_sensitive: Option<bool>,
1330 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1331 scroll_companion: Option<WeakEntity<Editor>>,
1332 on_local_selections_changed:
1333 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1334 suppress_selection_callback: bool,
1335 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1336 accent_data: Option<AccentData>,
1337 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1338}
1339
1340#[derive(Debug, PartialEq)]
1341struct AccentData {
1342 colors: AccentColors,
1343 overrides: Vec<SharedString>,
1344}
1345
1346fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1347 if debounce_ms > 0 {
1348 Some(Duration::from_millis(debounce_ms))
1349 } else {
1350 None
1351 }
1352}
1353
1354#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1355enum NextScrollCursorCenterTopBottom {
1356 #[default]
1357 Center,
1358 Top,
1359 Bottom,
1360}
1361
1362impl NextScrollCursorCenterTopBottom {
1363 fn next(&self) -> Self {
1364 match self {
1365 Self::Center => Self::Top,
1366 Self::Top => Self::Bottom,
1367 Self::Bottom => Self::Center,
1368 }
1369 }
1370}
1371
1372#[derive(Clone)]
1373pub struct EditorSnapshot {
1374 pub mode: EditorMode,
1375 show_gutter: bool,
1376 offset_content: bool,
1377 show_line_numbers: Option<bool>,
1378 number_deleted_lines: bool,
1379 show_git_diff_gutter: Option<bool>,
1380 show_code_actions: Option<bool>,
1381 show_runnables: Option<bool>,
1382 show_breakpoints: Option<bool>,
1383 git_blame_gutter_max_author_length: Option<usize>,
1384 pub display_snapshot: DisplaySnapshot,
1385 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1386 is_focused: bool,
1387 scroll_anchor: ScrollAnchor,
1388 ongoing_scroll: OngoingScroll,
1389 current_line_highlight: CurrentLineHighlight,
1390 gutter_hovered: bool,
1391}
1392
1393#[derive(Default, Debug, Clone, Copy)]
1394pub struct GutterDimensions {
1395 pub left_padding: Pixels,
1396 pub right_padding: Pixels,
1397 pub width: Pixels,
1398 pub margin: Pixels,
1399 pub git_blame_entries_width: Option<Pixels>,
1400}
1401
1402impl GutterDimensions {
1403 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1404 Self {
1405 margin: Self::default_gutter_margin(font_id, font_size, cx),
1406 ..Default::default()
1407 }
1408 }
1409
1410 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1411 -cx.text_system().descent(font_id, font_size)
1412 }
1413 /// The full width of the space taken up by the gutter.
1414 pub fn full_width(&self) -> Pixels {
1415 self.margin + self.width
1416 }
1417
1418 /// The width of the space reserved for the fold indicators,
1419 /// use alongside 'justify_end' and `gutter_width` to
1420 /// right align content with the line numbers
1421 pub fn fold_area_width(&self) -> Pixels {
1422 self.margin + self.right_padding
1423 }
1424}
1425
1426struct CharacterDimensions {
1427 em_width: Pixels,
1428 em_advance: Pixels,
1429 line_height: Pixels,
1430}
1431
1432#[derive(Debug)]
1433pub struct RemoteSelection {
1434 pub replica_id: ReplicaId,
1435 pub selection: Selection<Anchor>,
1436 pub cursor_shape: CursorShape,
1437 pub collaborator_id: CollaboratorId,
1438 pub line_mode: bool,
1439 pub user_name: Option<SharedString>,
1440 pub color: PlayerColor,
1441}
1442
1443#[derive(Clone, Debug)]
1444struct SelectionHistoryEntry {
1445 selections: Arc<[Selection<Anchor>]>,
1446 select_next_state: Option<SelectNextState>,
1447 select_prev_state: Option<SelectNextState>,
1448 add_selections_state: Option<AddSelectionsState>,
1449}
1450
1451#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1452enum SelectionHistoryMode {
1453 #[default]
1454 Normal,
1455 Undoing,
1456 Redoing,
1457 Skipping,
1458}
1459
1460#[derive(Clone, PartialEq, Eq, Hash)]
1461struct HoveredCursor {
1462 replica_id: ReplicaId,
1463 selection_id: usize,
1464}
1465
1466#[derive(Debug)]
1467/// SelectionEffects controls the side-effects of updating the selection.
1468///
1469/// The default behaviour does "what you mostly want":
1470/// - it pushes to the nav history if the cursor moved by >10 lines
1471/// - it re-triggers completion requests
1472/// - it scrolls to fit
1473///
1474/// You might want to modify these behaviours. For example when doing a "jump"
1475/// like go to definition, we always want to add to nav history; but when scrolling
1476/// in vim mode we never do.
1477///
1478/// Similarly, you might want to disable scrolling if you don't want the viewport to
1479/// move.
1480#[derive(Clone)]
1481pub struct SelectionEffects {
1482 nav_history: Option<bool>,
1483 completions: bool,
1484 scroll: Option<Autoscroll>,
1485}
1486
1487impl Default for SelectionEffects {
1488 fn default() -> Self {
1489 Self {
1490 nav_history: None,
1491 completions: true,
1492 scroll: Some(Autoscroll::fit()),
1493 }
1494 }
1495}
1496impl SelectionEffects {
1497 pub fn scroll(scroll: Autoscroll) -> Self {
1498 Self {
1499 scroll: Some(scroll),
1500 ..Default::default()
1501 }
1502 }
1503
1504 pub fn no_scroll() -> Self {
1505 Self {
1506 scroll: None,
1507 ..Default::default()
1508 }
1509 }
1510
1511 pub fn completions(self, completions: bool) -> Self {
1512 Self {
1513 completions,
1514 ..self
1515 }
1516 }
1517
1518 pub fn nav_history(self, nav_history: bool) -> Self {
1519 Self {
1520 nav_history: Some(nav_history),
1521 ..self
1522 }
1523 }
1524}
1525
1526struct DeferredSelectionEffectsState {
1527 changed: bool,
1528 effects: SelectionEffects,
1529 old_cursor_position: Anchor,
1530 history_entry: SelectionHistoryEntry,
1531}
1532
1533#[derive(Default)]
1534struct SelectionHistory {
1535 #[allow(clippy::type_complexity)]
1536 selections_by_transaction:
1537 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1538 mode: SelectionHistoryMode,
1539 undo_stack: VecDeque<SelectionHistoryEntry>,
1540 redo_stack: VecDeque<SelectionHistoryEntry>,
1541}
1542
1543impl SelectionHistory {
1544 #[track_caller]
1545 fn insert_transaction(
1546 &mut self,
1547 transaction_id: TransactionId,
1548 selections: Arc<[Selection<Anchor>]>,
1549 ) {
1550 if selections.is_empty() {
1551 log::error!(
1552 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1553 std::panic::Location::caller()
1554 );
1555 return;
1556 }
1557 self.selections_by_transaction
1558 .insert(transaction_id, (selections, None));
1559 }
1560
1561 #[allow(clippy::type_complexity)]
1562 fn transaction(
1563 &self,
1564 transaction_id: TransactionId,
1565 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1566 self.selections_by_transaction.get(&transaction_id)
1567 }
1568
1569 #[allow(clippy::type_complexity)]
1570 fn transaction_mut(
1571 &mut self,
1572 transaction_id: TransactionId,
1573 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1574 self.selections_by_transaction.get_mut(&transaction_id)
1575 }
1576
1577 fn push(&mut self, entry: SelectionHistoryEntry) {
1578 if !entry.selections.is_empty() {
1579 match self.mode {
1580 SelectionHistoryMode::Normal => {
1581 self.push_undo(entry);
1582 self.redo_stack.clear();
1583 }
1584 SelectionHistoryMode::Undoing => self.push_redo(entry),
1585 SelectionHistoryMode::Redoing => self.push_undo(entry),
1586 SelectionHistoryMode::Skipping => {}
1587 }
1588 }
1589 }
1590
1591 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1592 if self
1593 .undo_stack
1594 .back()
1595 .is_none_or(|e| e.selections != entry.selections)
1596 {
1597 self.undo_stack.push_back(entry);
1598 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1599 self.undo_stack.pop_front();
1600 }
1601 }
1602 }
1603
1604 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1605 if self
1606 .redo_stack
1607 .back()
1608 .is_none_or(|e| e.selections != entry.selections)
1609 {
1610 self.redo_stack.push_back(entry);
1611 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1612 self.redo_stack.pop_front();
1613 }
1614 }
1615 }
1616}
1617
1618#[derive(Clone, Copy)]
1619pub struct RowHighlightOptions {
1620 pub autoscroll: bool,
1621 pub include_gutter: bool,
1622}
1623
1624impl Default for RowHighlightOptions {
1625 fn default() -> Self {
1626 Self {
1627 autoscroll: Default::default(),
1628 include_gutter: true,
1629 }
1630 }
1631}
1632
1633struct RowHighlight {
1634 index: usize,
1635 range: Range<Anchor>,
1636 color: Hsla,
1637 options: RowHighlightOptions,
1638 type_id: TypeId,
1639}
1640
1641#[derive(Clone, Debug)]
1642struct AddSelectionsState {
1643 groups: Vec<AddSelectionsGroup>,
1644}
1645
1646#[derive(Clone, Debug)]
1647struct AddSelectionsGroup {
1648 above: bool,
1649 stack: Vec<usize>,
1650}
1651
1652#[derive(Clone)]
1653struct SelectNextState {
1654 query: AhoCorasick,
1655 wordwise: bool,
1656 done: bool,
1657}
1658
1659impl std::fmt::Debug for SelectNextState {
1660 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1661 f.debug_struct(std::any::type_name::<Self>())
1662 .field("wordwise", &self.wordwise)
1663 .field("done", &self.done)
1664 .finish()
1665 }
1666}
1667
1668#[derive(Debug)]
1669struct AutocloseRegion {
1670 selection_id: usize,
1671 range: Range<Anchor>,
1672 pair: BracketPair,
1673}
1674
1675#[derive(Debug)]
1676struct SnippetState {
1677 ranges: Vec<Vec<Range<Anchor>>>,
1678 active_index: usize,
1679 choices: Vec<Option<Vec<String>>>,
1680}
1681
1682#[doc(hidden)]
1683pub struct RenameState {
1684 pub range: Range<Anchor>,
1685 pub old_name: Arc<str>,
1686 pub editor: Entity<Editor>,
1687 block_id: CustomBlockId,
1688}
1689
1690struct InvalidationStack<T>(Vec<T>);
1691
1692struct RegisteredEditPredictionDelegate {
1693 provider: Arc<dyn EditPredictionDelegateHandle>,
1694 _subscription: Subscription,
1695}
1696
1697#[derive(Debug, PartialEq, Eq)]
1698pub struct ActiveDiagnosticGroup {
1699 pub active_range: Range<Anchor>,
1700 pub active_message: String,
1701 pub group_id: usize,
1702 pub blocks: HashSet<CustomBlockId>,
1703}
1704
1705#[derive(Debug, PartialEq, Eq)]
1706
1707pub(crate) enum ActiveDiagnostic {
1708 None,
1709 All,
1710 Group(ActiveDiagnosticGroup),
1711}
1712
1713#[derive(Serialize, Deserialize, Clone, Debug)]
1714pub struct ClipboardSelection {
1715 /// The number of bytes in this selection.
1716 pub len: usize,
1717 /// Whether this was a full-line selection.
1718 pub is_entire_line: bool,
1719 /// The indentation of the first line when this content was originally copied.
1720 pub first_line_indent: u32,
1721 #[serde(default)]
1722 pub file_path: Option<PathBuf>,
1723 #[serde(default)]
1724 pub line_range: Option<RangeInclusive<u32>>,
1725}
1726
1727impl ClipboardSelection {
1728 pub fn for_buffer(
1729 len: usize,
1730 is_entire_line: bool,
1731 range: Range<Point>,
1732 buffer: &MultiBufferSnapshot,
1733 project: Option<&Entity<Project>>,
1734 cx: &App,
1735 ) -> Self {
1736 let first_line_indent = buffer
1737 .indent_size_for_line(MultiBufferRow(range.start.row))
1738 .len;
1739
1740 let file_path = util::maybe!({
1741 let project = project?.read(cx);
1742 let file = buffer.file_at(range.start)?;
1743 let project_path = ProjectPath {
1744 worktree_id: file.worktree_id(cx),
1745 path: file.path().clone(),
1746 };
1747 project.absolute_path(&project_path, cx)
1748 });
1749
1750 let line_range = file_path.as_ref().and_then(|_| {
1751 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1752 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1753 if start_excerpt_id == end_excerpt_id {
1754 Some(start_point.row..=end_point.row)
1755 } else {
1756 None
1757 }
1758 });
1759
1760 Self {
1761 len,
1762 is_entire_line,
1763 first_line_indent,
1764 file_path,
1765 line_range,
1766 }
1767 }
1768}
1769
1770// selections, scroll behavior, was newest selection reversed
1771type SelectSyntaxNodeHistoryState = (
1772 Box<[Selection<MultiBufferOffset>]>,
1773 SelectSyntaxNodeScrollBehavior,
1774 bool,
1775);
1776
1777#[derive(Default)]
1778struct SelectSyntaxNodeHistory {
1779 stack: Vec<SelectSyntaxNodeHistoryState>,
1780 // disable temporarily to allow changing selections without losing the stack
1781 pub disable_clearing: bool,
1782}
1783
1784impl SelectSyntaxNodeHistory {
1785 pub fn try_clear(&mut self) {
1786 if !self.disable_clearing {
1787 self.stack.clear();
1788 }
1789 }
1790
1791 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1792 self.stack.push(selection);
1793 }
1794
1795 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1796 self.stack.pop()
1797 }
1798}
1799
1800enum SelectSyntaxNodeScrollBehavior {
1801 CursorTop,
1802 FitSelection,
1803 CursorBottom,
1804}
1805
1806#[derive(Debug, Clone, Copy)]
1807pub(crate) struct NavigationData {
1808 cursor_anchor: Anchor,
1809 cursor_position: Point,
1810 scroll_anchor: ScrollAnchor,
1811 scroll_top_row: u32,
1812}
1813
1814#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1815pub enum GotoDefinitionKind {
1816 Symbol,
1817 Declaration,
1818 Type,
1819 Implementation,
1820}
1821
1822pub enum FormatTarget {
1823 Buffers(HashSet<Entity<Buffer>>),
1824 Ranges(Vec<Range<MultiBufferPoint>>),
1825}
1826
1827pub(crate) struct FocusedBlock {
1828 id: BlockId,
1829 focus_handle: WeakFocusHandle,
1830}
1831
1832#[derive(Clone, Debug)]
1833pub enum JumpData {
1834 MultiBufferRow {
1835 row: MultiBufferRow,
1836 line_offset_from_top: u32,
1837 },
1838 MultiBufferPoint {
1839 excerpt_id: ExcerptId,
1840 position: Point,
1841 anchor: text::Anchor,
1842 line_offset_from_top: u32,
1843 },
1844}
1845
1846pub enum MultibufferSelectionMode {
1847 First,
1848 All,
1849}
1850
1851#[derive(Clone, Copy, Debug, Default)]
1852pub struct RewrapOptions {
1853 pub override_language_settings: bool,
1854 pub preserve_existing_whitespace: bool,
1855}
1856
1857impl Editor {
1858 pub fn single_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::SingleLine, buffer, None, window, cx)
1862 }
1863
1864 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1865 let buffer = cx.new(|cx| Buffer::local("", cx));
1866 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1867 Self::new(EditorMode::full(), buffer, None, window, cx)
1868 }
1869
1870 pub fn auto_height(
1871 min_lines: usize,
1872 max_lines: usize,
1873 window: &mut Window,
1874 cx: &mut Context<Self>,
1875 ) -> Self {
1876 let buffer = cx.new(|cx| Buffer::local("", cx));
1877 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1878 Self::new(
1879 EditorMode::AutoHeight {
1880 min_lines,
1881 max_lines: Some(max_lines),
1882 },
1883 buffer,
1884 None,
1885 window,
1886 cx,
1887 )
1888 }
1889
1890 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1891 /// The editor grows as tall as needed to fit its content.
1892 pub fn auto_height_unbounded(
1893 min_lines: usize,
1894 window: &mut Window,
1895 cx: &mut Context<Self>,
1896 ) -> Self {
1897 let buffer = cx.new(|cx| Buffer::local("", cx));
1898 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1899 Self::new(
1900 EditorMode::AutoHeight {
1901 min_lines,
1902 max_lines: None,
1903 },
1904 buffer,
1905 None,
1906 window,
1907 cx,
1908 )
1909 }
1910
1911 pub fn for_buffer(
1912 buffer: Entity<Buffer>,
1913 project: Option<Entity<Project>>,
1914 window: &mut Window,
1915 cx: &mut Context<Self>,
1916 ) -> Self {
1917 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1918 Self::new(EditorMode::full(), buffer, project, window, cx)
1919 }
1920
1921 pub fn for_multibuffer(
1922 buffer: Entity<MultiBuffer>,
1923 project: Option<Entity<Project>>,
1924 window: &mut Window,
1925 cx: &mut Context<Self>,
1926 ) -> Self {
1927 Self::new(EditorMode::full(), buffer, project, window, cx)
1928 }
1929
1930 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1931 let mut clone = Self::new(
1932 self.mode.clone(),
1933 self.buffer.clone(),
1934 self.project.clone(),
1935 window,
1936 cx,
1937 );
1938 self.display_map.update(cx, |display_map, cx| {
1939 let snapshot = display_map.snapshot(cx);
1940 clone.display_map.update(cx, |display_map, cx| {
1941 display_map.set_state(&snapshot, cx);
1942 });
1943 });
1944 clone.folds_did_change(cx);
1945 clone.selections.clone_state(&self.selections);
1946 clone.scroll_manager.clone_state(&self.scroll_manager);
1947 clone.searchable = self.searchable;
1948 clone.read_only = self.read_only;
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 sticky_headers(
1963 &self,
1964 style: &EditorStyle,
1965 cx: &App,
1966 ) -> Option<Vec<OutlineItem<Anchor>>> {
1967 let multi_buffer = self.buffer().read(cx);
1968 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1969 let multi_buffer_visible_start = self
1970 .scroll_manager
1971 .anchor()
1972 .anchor
1973 .to_point(&multi_buffer_snapshot);
1974 let max_row = multi_buffer_snapshot.max_point().row;
1975
1976 let start_row = (multi_buffer_visible_start.row).min(max_row);
1977 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1978
1979 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1980 let outline_items = buffer
1981 .outline_items_containing(
1982 Point::new(start_row, 0)..Point::new(end_row, 0),
1983 true,
1984 Some(style.syntax.as_ref()),
1985 )
1986 .into_iter()
1987 .map(|outline_item| OutlineItem {
1988 depth: outline_item.depth,
1989 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1990 source_range_for_text: Anchor::range_in_buffer(
1991 *excerpt_id,
1992 outline_item.source_range_for_text,
1993 ),
1994 text: outline_item.text,
1995 highlight_ranges: outline_item.highlight_ranges,
1996 name_ranges: outline_item.name_ranges,
1997 body_range: outline_item
1998 .body_range
1999 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
2000 annotation_range: outline_item
2001 .annotation_range
2002 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
2003 });
2004 return Some(outline_items.collect());
2005 }
2006
2007 None
2008 }
2009
2010 fn new_internal(
2011 mode: EditorMode,
2012 multi_buffer: Entity<MultiBuffer>,
2013 project: Option<Entity<Project>>,
2014 display_map: Option<Entity<DisplayMap>>,
2015 window: &mut Window,
2016 cx: &mut Context<Self>,
2017 ) -> Self {
2018 debug_assert!(
2019 display_map.is_none() || mode.is_minimap(),
2020 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2021 );
2022
2023 let full_mode = mode.is_full();
2024 let is_minimap = mode.is_minimap();
2025 let diagnostics_max_severity = if full_mode {
2026 EditorSettings::get_global(cx)
2027 .diagnostics_max_severity
2028 .unwrap_or(DiagnosticSeverity::Hint)
2029 } else {
2030 DiagnosticSeverity::Off
2031 };
2032 let style = window.text_style();
2033 let font_size = style.font_size.to_pixels(window.rem_size());
2034 let editor = cx.entity().downgrade();
2035 let fold_placeholder = FoldPlaceholder {
2036 constrain_width: false,
2037 render: Arc::new(move |fold_id, fold_range, cx| {
2038 let editor = editor.clone();
2039 div()
2040 .id(fold_id)
2041 .bg(cx.theme().colors().ghost_element_background)
2042 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
2043 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
2044 .rounded_xs()
2045 .size_full()
2046 .cursor_pointer()
2047 .child("⋯")
2048 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2049 .on_click(move |_, _window, cx| {
2050 editor
2051 .update(cx, |editor, cx| {
2052 editor.unfold_ranges(
2053 &[fold_range.start..fold_range.end],
2054 true,
2055 false,
2056 cx,
2057 );
2058 cx.stop_propagation();
2059 })
2060 .ok();
2061 })
2062 .into_any()
2063 }),
2064 merge_adjacent: true,
2065 ..FoldPlaceholder::default()
2066 };
2067 let display_map = display_map.unwrap_or_else(|| {
2068 cx.new(|cx| {
2069 DisplayMap::new(
2070 multi_buffer.clone(),
2071 style.font(),
2072 font_size,
2073 None,
2074 FILE_HEADER_HEIGHT,
2075 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2076 fold_placeholder,
2077 diagnostics_max_severity,
2078 cx,
2079 )
2080 })
2081 });
2082
2083 let selections = SelectionsCollection::new();
2084
2085 let blink_manager = cx.new(|cx| {
2086 let mut blink_manager = BlinkManager::new(
2087 CURSOR_BLINK_INTERVAL,
2088 |cx| EditorSettings::get_global(cx).cursor_blink,
2089 cx,
2090 );
2091 if is_minimap {
2092 blink_manager.disable(cx);
2093 }
2094 blink_manager
2095 });
2096
2097 let soft_wrap_mode_override =
2098 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2099
2100 let mut project_subscriptions = Vec::new();
2101 if full_mode && let Some(project) = project.as_ref() {
2102 project_subscriptions.push(cx.subscribe_in(
2103 project,
2104 window,
2105 |editor, _, event, window, cx| match event {
2106 project::Event::RefreshCodeLens => {
2107 // we always query lens with actions, without storing them, always refreshing them
2108 }
2109 project::Event::RefreshInlayHints {
2110 server_id,
2111 request_id,
2112 } => {
2113 editor.refresh_inlay_hints(
2114 InlayHintRefreshReason::RefreshRequested {
2115 server_id: *server_id,
2116 request_id: *request_id,
2117 },
2118 cx,
2119 );
2120 }
2121 project::Event::LanguageServerRemoved(..) => {
2122 if editor.tasks_update_task.is_none() {
2123 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2124 }
2125 editor.registered_buffers.clear();
2126 editor.register_visible_buffers(cx);
2127 }
2128 project::Event::LanguageServerAdded(..) => {
2129 if editor.tasks_update_task.is_none() {
2130 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2131 }
2132 }
2133 project::Event::SnippetEdit(id, snippet_edits) => {
2134 // todo(lw): Non singletons
2135 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2136 let snapshot = buffer.read(cx).snapshot();
2137 let focus_handle = editor.focus_handle(cx);
2138 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2139 for (range, snippet) in snippet_edits {
2140 let buffer_range =
2141 language::range_from_lsp(*range).to_offset(&snapshot);
2142 editor
2143 .insert_snippet(
2144 &[MultiBufferOffset(buffer_range.start)
2145 ..MultiBufferOffset(buffer_range.end)],
2146 snippet.clone(),
2147 window,
2148 cx,
2149 )
2150 .ok();
2151 }
2152 }
2153 }
2154 }
2155 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2156 let buffer_id = *buffer_id;
2157 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2158 editor.register_buffer(buffer_id, cx);
2159 editor.update_lsp_data(Some(buffer_id), window, cx);
2160 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2161 refresh_linked_ranges(editor, window, cx);
2162 editor.refresh_code_actions(window, cx);
2163 editor.refresh_document_highlights(cx);
2164 }
2165 }
2166
2167 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2168 let Some(workspace) = editor.workspace() else {
2169 return;
2170 };
2171 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2172 else {
2173 return;
2174 };
2175
2176 if active_editor.entity_id() == cx.entity_id() {
2177 let entity_id = cx.entity_id();
2178 workspace.update(cx, |this, cx| {
2179 this.panes_mut()
2180 .iter_mut()
2181 .filter(|pane| pane.entity_id() != entity_id)
2182 .for_each(|p| {
2183 p.update(cx, |pane, _| {
2184 pane.nav_history_mut().rename_item(
2185 entity_id,
2186 project_path.clone(),
2187 abs_path.clone().into(),
2188 );
2189 })
2190 });
2191 });
2192
2193 Self::open_transaction_for_hidden_buffers(
2194 workspace,
2195 transaction.clone(),
2196 "Rename".to_string(),
2197 window,
2198 cx,
2199 );
2200 }
2201 }
2202
2203 project::Event::WorkspaceEditApplied(transaction) => {
2204 let Some(workspace) = editor.workspace() else {
2205 return;
2206 };
2207 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2208 else {
2209 return;
2210 };
2211
2212 if active_editor.entity_id() == cx.entity_id() {
2213 Self::open_transaction_for_hidden_buffers(
2214 workspace,
2215 transaction.clone(),
2216 "LSP Edit".to_string(),
2217 window,
2218 cx,
2219 );
2220 }
2221 }
2222
2223 _ => {}
2224 },
2225 ));
2226 if let Some(task_inventory) = project
2227 .read(cx)
2228 .task_store()
2229 .read(cx)
2230 .task_inventory()
2231 .cloned()
2232 {
2233 project_subscriptions.push(cx.observe_in(
2234 &task_inventory,
2235 window,
2236 |editor, _, window, cx| {
2237 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2238 },
2239 ));
2240 };
2241
2242 project_subscriptions.push(cx.subscribe_in(
2243 &project.read(cx).breakpoint_store(),
2244 window,
2245 |editor, _, event, window, cx| match event {
2246 BreakpointStoreEvent::ClearDebugLines => {
2247 editor.clear_row_highlights::<ActiveDebugLine>();
2248 editor.refresh_inline_values(cx);
2249 }
2250 BreakpointStoreEvent::SetDebugLine => {
2251 if editor.go_to_active_debug_line(window, cx) {
2252 cx.stop_propagation();
2253 }
2254
2255 editor.refresh_inline_values(cx);
2256 }
2257 _ => {}
2258 },
2259 ));
2260 let git_store = project.read(cx).git_store().clone();
2261 let project = project.clone();
2262 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2263 if let GitStoreEvent::RepositoryAdded = event {
2264 this.load_diff_task = Some(
2265 update_uncommitted_diff_for_buffer(
2266 cx.entity(),
2267 &project,
2268 this.buffer.read(cx).all_buffers(),
2269 this.buffer.clone(),
2270 cx,
2271 )
2272 .shared(),
2273 );
2274 }
2275 }));
2276 }
2277
2278 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2279
2280 let inlay_hint_settings =
2281 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2282 let focus_handle = cx.focus_handle();
2283 if !is_minimap {
2284 cx.on_focus(&focus_handle, window, Self::handle_focus)
2285 .detach();
2286 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2287 .detach();
2288 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2289 .detach();
2290 cx.on_blur(&focus_handle, window, Self::handle_blur)
2291 .detach();
2292 cx.observe_pending_input(window, Self::observe_pending_input)
2293 .detach();
2294 }
2295
2296 let show_indent_guides =
2297 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2298 Some(false)
2299 } else {
2300 None
2301 };
2302
2303 let breakpoint_store = match (&mode, project.as_ref()) {
2304 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2305 _ => None,
2306 };
2307
2308 let mut code_action_providers = Vec::new();
2309 let mut load_uncommitted_diff = None;
2310 if let Some(project) = project.clone() {
2311 load_uncommitted_diff = Some(
2312 update_uncommitted_diff_for_buffer(
2313 cx.entity(),
2314 &project,
2315 multi_buffer.read(cx).all_buffers(),
2316 multi_buffer.clone(),
2317 cx,
2318 )
2319 .shared(),
2320 );
2321 code_action_providers.push(Rc::new(project) as Rc<_>);
2322 }
2323
2324 let mut editor = Self {
2325 focus_handle,
2326 show_cursor_when_unfocused: false,
2327 last_focused_descendant: None,
2328 buffer: multi_buffer.clone(),
2329 display_map: display_map.clone(),
2330 placeholder_display_map: None,
2331 selections,
2332 scroll_manager: ScrollManager::new(cx),
2333 columnar_selection_state: None,
2334 add_selections_state: None,
2335 select_next_state: None,
2336 select_prev_state: None,
2337 selection_history: SelectionHistory::default(),
2338 defer_selection_effects: false,
2339 deferred_selection_effects_state: None,
2340 autoclose_regions: Vec::new(),
2341 snippet_stack: InvalidationStack::default(),
2342 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2343 ime_transaction: None,
2344 active_diagnostics: ActiveDiagnostic::None,
2345 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2346 inline_diagnostics_update: Task::ready(()),
2347 inline_diagnostics: Vec::new(),
2348 soft_wrap_mode_override,
2349 diagnostics_max_severity,
2350 hard_wrap: None,
2351 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2352 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2353 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2354 project,
2355 blink_manager: blink_manager.clone(),
2356 show_local_selections: true,
2357 show_scrollbars: ScrollbarAxes {
2358 horizontal: full_mode,
2359 vertical: full_mode,
2360 },
2361 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2362 offset_content: !matches!(mode, EditorMode::SingleLine),
2363 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2364 show_gutter: full_mode,
2365 show_line_numbers: (!full_mode).then_some(false),
2366 use_relative_line_numbers: None,
2367 disable_expand_excerpt_buttons: !full_mode,
2368 delegate_expand_excerpts: false,
2369 show_git_diff_gutter: None,
2370 show_code_actions: None,
2371 show_runnables: None,
2372 show_breakpoints: None,
2373 show_diff_review_button: false,
2374 show_wrap_guides: None,
2375 show_indent_guides,
2376 buffers_with_disabled_indent_guides: HashSet::default(),
2377 highlight_order: 0,
2378 highlighted_rows: HashMap::default(),
2379 background_highlights: HashMap::default(),
2380 gutter_highlights: HashMap::default(),
2381 scrollbar_marker_state: ScrollbarMarkerState::default(),
2382 active_indent_guides_state: ActiveIndentGuidesState::default(),
2383 nav_history: None,
2384 context_menu: RefCell::new(None),
2385 context_menu_options: None,
2386 mouse_context_menu: None,
2387 completion_tasks: Vec::new(),
2388 inline_blame_popover: None,
2389 inline_blame_popover_show_task: None,
2390 signature_help_state: SignatureHelpState::default(),
2391 auto_signature_help: None,
2392 find_all_references_task_sources: Vec::new(),
2393 next_completion_id: 0,
2394 next_inlay_id: 0,
2395 code_action_providers,
2396 available_code_actions: None,
2397 code_actions_task: None,
2398 quick_selection_highlight_task: None,
2399 debounced_selection_highlight_task: None,
2400 debounced_selection_highlight_complete: false,
2401 document_highlights_task: None,
2402 linked_editing_range_task: None,
2403 pending_rename: None,
2404 searchable: !is_minimap,
2405 cursor_shape: EditorSettings::get_global(cx)
2406 .cursor_shape
2407 .unwrap_or_default(),
2408 cursor_offset_on_selection: false,
2409 current_line_highlight: None,
2410 autoindent_mode: Some(AutoindentMode::EachLine),
2411 collapse_matches: false,
2412 workspace: None,
2413 input_enabled: !is_minimap,
2414 use_modal_editing: full_mode,
2415 read_only: is_minimap,
2416 use_autoclose: true,
2417 use_auto_surround: true,
2418 auto_replace_emoji_shortcode: false,
2419 jsx_tag_auto_close_enabled_in_any_buffer: false,
2420 leader_id: None,
2421 remote_id: None,
2422 hover_state: HoverState::default(),
2423 pending_mouse_down: None,
2424 prev_pressure_stage: None,
2425 hovered_link_state: None,
2426 edit_prediction_provider: None,
2427 active_edit_prediction: None,
2428 stale_edit_prediction_in_menu: None,
2429 edit_prediction_preview: EditPredictionPreview::Inactive {
2430 released_too_fast: false,
2431 },
2432 inline_diagnostics_enabled: full_mode,
2433 diagnostics_enabled: full_mode,
2434 word_completions_enabled: full_mode,
2435 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2436 gutter_hovered: false,
2437 pixel_position_of_newest_cursor: None,
2438 last_bounds: None,
2439 last_position_map: None,
2440 expect_bounds_change: None,
2441 gutter_dimensions: GutterDimensions::default(),
2442 style: None,
2443 show_cursor_names: false,
2444 hovered_cursors: HashMap::default(),
2445 next_editor_action_id: EditorActionId::default(),
2446 editor_actions: Rc::default(),
2447 edit_predictions_hidden_for_vim_mode: false,
2448 show_edit_predictions_override: None,
2449 show_completions_on_input_override: None,
2450 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2451 edit_prediction_settings: EditPredictionSettings::Disabled,
2452 edit_prediction_indent_conflict: false,
2453 edit_prediction_requires_modifier_in_indent_conflict: true,
2454 custom_context_menu: None,
2455 show_git_blame_gutter: false,
2456 show_git_blame_inline: false,
2457 show_selection_menu: None,
2458 show_git_blame_inline_delay_task: None,
2459 git_blame_inline_enabled: full_mode
2460 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2461 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2462 buffer_serialization: is_minimap.not().then(|| {
2463 BufferSerialization::new(
2464 ProjectSettings::get_global(cx)
2465 .session
2466 .restore_unsaved_buffers,
2467 )
2468 }),
2469 blame: None,
2470 blame_subscription: None,
2471 tasks: BTreeMap::default(),
2472
2473 breakpoint_store,
2474 gutter_breakpoint_indicator: (None, None),
2475 gutter_diff_review_indicator: (None, None),
2476 diff_review_drag_state: None,
2477 diff_review_overlays: Vec::new(),
2478 stored_review_comments: Vec::new(),
2479 next_review_comment_id: 0,
2480 hovered_diff_hunk_row: None,
2481 _subscriptions: (!is_minimap)
2482 .then(|| {
2483 vec![
2484 cx.observe(&multi_buffer, Self::on_buffer_changed),
2485 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2486 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2487 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2488 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2489 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2490 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2491 cx.observe_window_activation(window, |editor, window, cx| {
2492 let active = window.is_window_active();
2493 editor.blink_manager.update(cx, |blink_manager, cx| {
2494 if active {
2495 blink_manager.enable(cx);
2496 } else {
2497 blink_manager.disable(cx);
2498 }
2499 });
2500 if active {
2501 editor.show_mouse_cursor(cx);
2502 }
2503 }),
2504 ]
2505 })
2506 .unwrap_or_default(),
2507 tasks_update_task: None,
2508 pull_diagnostics_task: Task::ready(()),
2509 colors: None,
2510 refresh_colors_task: Task::ready(()),
2511 inlay_hints: None,
2512 next_color_inlay_id: 0,
2513 post_scroll_update: Task::ready(()),
2514 linked_edit_ranges: Default::default(),
2515 in_project_search: false,
2516 previous_search_ranges: None,
2517 breadcrumb_header: None,
2518 focused_block: None,
2519 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2520 addons: HashMap::default(),
2521 registered_buffers: HashMap::default(),
2522 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2523 selection_mark_mode: false,
2524 toggle_fold_multiple_buffers: Task::ready(()),
2525 serialize_selections: Task::ready(()),
2526 serialize_folds: Task::ready(()),
2527 text_style_refinement: None,
2528 load_diff_task: load_uncommitted_diff,
2529 temporary_diff_override: false,
2530 mouse_cursor_hidden: false,
2531 minimap: None,
2532 hide_mouse_mode: EditorSettings::get_global(cx)
2533 .hide_mouse
2534 .unwrap_or_default(),
2535 change_list: ChangeList::new(),
2536 mode,
2537 selection_drag_state: SelectionDragState::None,
2538 folding_newlines: Task::ready(()),
2539 lookup_key: None,
2540 select_next_is_case_sensitive: None,
2541 scroll_companion: None,
2542 on_local_selections_changed: None,
2543 suppress_selection_callback: false,
2544 applicable_language_settings: HashMap::default(),
2545 accent_data: None,
2546 fetched_tree_sitter_chunks: HashMap::default(),
2547 number_deleted_lines: false,
2548 };
2549
2550 if is_minimap {
2551 return editor;
2552 }
2553
2554 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2555 editor.accent_data = editor.fetch_accent_data(cx);
2556
2557 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2558 editor
2559 ._subscriptions
2560 .push(cx.observe(breakpoints, |_, _, cx| {
2561 cx.notify();
2562 }));
2563 }
2564 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2565 editor._subscriptions.extend(project_subscriptions);
2566
2567 editor._subscriptions.push(cx.subscribe_in(
2568 &cx.entity(),
2569 window,
2570 |editor, _, e: &EditorEvent, window, cx| match e {
2571 EditorEvent::ScrollPositionChanged { local, .. } => {
2572 if *local {
2573 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2574 editor.inline_blame_popover.take();
2575 let new_anchor = editor.scroll_manager.anchor();
2576 let snapshot = editor.snapshot(window, cx);
2577 editor.update_restoration_data(cx, move |data| {
2578 data.scroll_position = (
2579 new_anchor.top_row(snapshot.buffer_snapshot()),
2580 new_anchor.offset,
2581 );
2582 });
2583
2584 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2585 cx.background_executor()
2586 .timer(Duration::from_millis(50))
2587 .await;
2588 editor
2589 .update_in(cx, |editor, window, cx| {
2590 editor.register_visible_buffers(cx);
2591 editor.refresh_colors_for_visible_range(None, window, cx);
2592 editor.refresh_inlay_hints(
2593 InlayHintRefreshReason::NewLinesShown,
2594 cx,
2595 );
2596 editor.colorize_brackets(false, cx);
2597 })
2598 .ok();
2599 });
2600 }
2601 }
2602 EditorEvent::Edited { .. } => {
2603 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2604 .map(|vim_mode| vim_mode.0)
2605 .unwrap_or(false);
2606 if !vim_mode {
2607 let display_map = editor.display_snapshot(cx);
2608 let selections = editor.selections.all_adjusted_display(&display_map);
2609 let pop_state = editor
2610 .change_list
2611 .last()
2612 .map(|previous| {
2613 previous.len() == selections.len()
2614 && previous.iter().enumerate().all(|(ix, p)| {
2615 p.to_display_point(&display_map).row()
2616 == selections[ix].head().row()
2617 })
2618 })
2619 .unwrap_or(false);
2620 let new_positions = selections
2621 .into_iter()
2622 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2623 .collect();
2624 editor
2625 .change_list
2626 .push_to_change_list(pop_state, new_positions);
2627 }
2628 }
2629 _ => (),
2630 },
2631 ));
2632
2633 if let Some(dap_store) = editor
2634 .project
2635 .as_ref()
2636 .map(|project| project.read(cx).dap_store())
2637 {
2638 let weak_editor = cx.weak_entity();
2639
2640 editor
2641 ._subscriptions
2642 .push(
2643 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2644 let session_entity = cx.entity();
2645 weak_editor
2646 .update(cx, |editor, cx| {
2647 editor._subscriptions.push(
2648 cx.subscribe(&session_entity, Self::on_debug_session_event),
2649 );
2650 })
2651 .ok();
2652 }),
2653 );
2654
2655 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2656 editor
2657 ._subscriptions
2658 .push(cx.subscribe(&session, Self::on_debug_session_event));
2659 }
2660 }
2661
2662 // skip adding the initial selection to selection history
2663 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2664 editor.end_selection(window, cx);
2665 editor.selection_history.mode = SelectionHistoryMode::Normal;
2666
2667 editor.scroll_manager.show_scrollbars(window, cx);
2668 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2669
2670 if full_mode {
2671 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2672 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2673
2674 if editor.git_blame_inline_enabled {
2675 editor.start_git_blame_inline(false, window, cx);
2676 }
2677
2678 editor.go_to_active_debug_line(window, cx);
2679
2680 editor.minimap =
2681 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2682 editor.colors = Some(LspColorData::new(cx));
2683 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2684
2685 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2686 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2687 }
2688 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2689 }
2690
2691 editor
2692 }
2693
2694 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2695 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2696 }
2697
2698 pub fn deploy_mouse_context_menu(
2699 &mut self,
2700 position: gpui::Point<Pixels>,
2701 context_menu: Entity<ContextMenu>,
2702 window: &mut Window,
2703 cx: &mut Context<Self>,
2704 ) {
2705 self.mouse_context_menu = Some(MouseContextMenu::new(
2706 self,
2707 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2708 context_menu,
2709 window,
2710 cx,
2711 ));
2712 }
2713
2714 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2715 self.mouse_context_menu
2716 .as_ref()
2717 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2718 }
2719
2720 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2721 if self
2722 .selections
2723 .pending_anchor()
2724 .is_some_and(|pending_selection| {
2725 let snapshot = self.buffer().read(cx).snapshot(cx);
2726 pending_selection.range().includes(range, &snapshot)
2727 })
2728 {
2729 return true;
2730 }
2731
2732 self.selections
2733 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2734 .into_iter()
2735 .any(|selection| {
2736 // This is needed to cover a corner case, if we just check for an existing
2737 // selection in the fold range, having a cursor at the start of the fold
2738 // marks it as selected. Non-empty selections don't cause this.
2739 let length = selection.end - selection.start;
2740 length > 0
2741 })
2742 }
2743
2744 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2745 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2746 }
2747
2748 fn key_context_internal(
2749 &self,
2750 has_active_edit_prediction: bool,
2751 window: &mut Window,
2752 cx: &mut App,
2753 ) -> KeyContext {
2754 let mut key_context = KeyContext::new_with_defaults();
2755 key_context.add("Editor");
2756 let mode = match self.mode {
2757 EditorMode::SingleLine => "single_line",
2758 EditorMode::AutoHeight { .. } => "auto_height",
2759 EditorMode::Minimap { .. } => "minimap",
2760 EditorMode::Full { .. } => "full",
2761 };
2762
2763 if EditorSettings::jupyter_enabled(cx) {
2764 key_context.add("jupyter");
2765 }
2766
2767 key_context.set("mode", mode);
2768 if self.pending_rename.is_some() {
2769 key_context.add("renaming");
2770 }
2771
2772 if let Some(snippet_stack) = self.snippet_stack.last() {
2773 key_context.add("in_snippet");
2774
2775 if snippet_stack.active_index > 0 {
2776 key_context.add("has_previous_tabstop");
2777 }
2778
2779 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2780 key_context.add("has_next_tabstop");
2781 }
2782 }
2783
2784 match self.context_menu.borrow().as_ref() {
2785 Some(CodeContextMenu::Completions(menu)) => {
2786 if menu.visible() {
2787 key_context.add("menu");
2788 key_context.add("showing_completions");
2789 }
2790 }
2791 Some(CodeContextMenu::CodeActions(menu)) => {
2792 if menu.visible() {
2793 key_context.add("menu");
2794 key_context.add("showing_code_actions")
2795 }
2796 }
2797 None => {}
2798 }
2799
2800 if self.signature_help_state.has_multiple_signatures() {
2801 key_context.add("showing_signature_help");
2802 }
2803
2804 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2805 if !self.focus_handle(cx).contains_focused(window, cx)
2806 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2807 {
2808 for addon in self.addons.values() {
2809 addon.extend_key_context(&mut key_context, cx)
2810 }
2811 }
2812
2813 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2814 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2815 Some(
2816 file.full_path(cx)
2817 .extension()?
2818 .to_string_lossy()
2819 .to_lowercase(),
2820 )
2821 }) {
2822 key_context.set("extension", extension);
2823 }
2824 } else {
2825 key_context.add("multibuffer");
2826 }
2827
2828 if has_active_edit_prediction {
2829 if self.edit_prediction_in_conflict() {
2830 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2831 } else {
2832 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2833 key_context.add("copilot_suggestion");
2834 }
2835 }
2836
2837 if self.selection_mark_mode {
2838 key_context.add("selection_mode");
2839 }
2840
2841 let disjoint = self.selections.disjoint_anchors();
2842 let snapshot = self.snapshot(window, cx);
2843 let snapshot = snapshot.buffer_snapshot();
2844 if self.mode == EditorMode::SingleLine
2845 && let [selection] = disjoint
2846 && selection.start == selection.end
2847 && selection.end.to_offset(snapshot) == snapshot.len()
2848 {
2849 key_context.add("end_of_input");
2850 }
2851
2852 if self.has_any_expanded_diff_hunks(cx) {
2853 key_context.add("diffs_expanded");
2854 }
2855
2856 key_context
2857 }
2858
2859 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2860 self.last_bounds.as_ref()
2861 }
2862
2863 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2864 if self.mouse_cursor_hidden {
2865 self.mouse_cursor_hidden = false;
2866 cx.notify();
2867 }
2868 }
2869
2870 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2871 let hide_mouse_cursor = match origin {
2872 HideMouseCursorOrigin::TypingAction => {
2873 matches!(
2874 self.hide_mouse_mode,
2875 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2876 )
2877 }
2878 HideMouseCursorOrigin::MovementAction => {
2879 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2880 }
2881 };
2882 if self.mouse_cursor_hidden != hide_mouse_cursor {
2883 self.mouse_cursor_hidden = hide_mouse_cursor;
2884 cx.notify();
2885 }
2886 }
2887
2888 pub fn edit_prediction_in_conflict(&self) -> bool {
2889 if !self.show_edit_predictions_in_menu() {
2890 return false;
2891 }
2892
2893 let showing_completions = self
2894 .context_menu
2895 .borrow()
2896 .as_ref()
2897 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2898
2899 showing_completions
2900 || self.edit_prediction_requires_modifier()
2901 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2902 // bindings to insert tab characters.
2903 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2904 }
2905
2906 pub fn accept_edit_prediction_keybind(
2907 &self,
2908 granularity: EditPredictionGranularity,
2909 window: &mut Window,
2910 cx: &mut App,
2911 ) -> AcceptEditPredictionBinding {
2912 let key_context = self.key_context_internal(true, window, cx);
2913 let in_conflict = self.edit_prediction_in_conflict();
2914
2915 let bindings =
2916 match granularity {
2917 EditPredictionGranularity::Word => window
2918 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2919 EditPredictionGranularity::Line => window
2920 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2921 EditPredictionGranularity::Full => {
2922 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2923 }
2924 };
2925
2926 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2927 !in_conflict
2928 || binding
2929 .keystrokes()
2930 .first()
2931 .is_some_and(|keystroke| keystroke.modifiers().modified())
2932 }))
2933 }
2934
2935 pub fn new_file(
2936 workspace: &mut Workspace,
2937 _: &workspace::NewFile,
2938 window: &mut Window,
2939 cx: &mut Context<Workspace>,
2940 ) {
2941 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2942 "Failed to create buffer",
2943 window,
2944 cx,
2945 |e, _, _| match e.error_code() {
2946 ErrorCode::RemoteUpgradeRequired => Some(format!(
2947 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2948 e.error_tag("required").unwrap_or("the latest version")
2949 )),
2950 _ => None,
2951 },
2952 );
2953 }
2954
2955 pub fn new_in_workspace(
2956 workspace: &mut Workspace,
2957 window: &mut Window,
2958 cx: &mut Context<Workspace>,
2959 ) -> Task<Result<Entity<Editor>>> {
2960 let project = workspace.project().clone();
2961 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
2962
2963 cx.spawn_in(window, async move |workspace, cx| {
2964 let buffer = create.await?;
2965 workspace.update_in(cx, |workspace, window, cx| {
2966 let editor =
2967 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2968 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2969 editor
2970 })
2971 })
2972 }
2973
2974 fn new_file_vertical(
2975 workspace: &mut Workspace,
2976 _: &workspace::NewFileSplitVertical,
2977 window: &mut Window,
2978 cx: &mut Context<Workspace>,
2979 ) {
2980 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2981 }
2982
2983 fn new_file_horizontal(
2984 workspace: &mut Workspace,
2985 _: &workspace::NewFileSplitHorizontal,
2986 window: &mut Window,
2987 cx: &mut Context<Workspace>,
2988 ) {
2989 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2990 }
2991
2992 fn new_file_split(
2993 workspace: &mut Workspace,
2994 action: &workspace::NewFileSplit,
2995 window: &mut Window,
2996 cx: &mut Context<Workspace>,
2997 ) {
2998 Self::new_file_in_direction(workspace, action.0, window, cx)
2999 }
3000
3001 fn new_file_in_direction(
3002 workspace: &mut Workspace,
3003 direction: SplitDirection,
3004 window: &mut Window,
3005 cx: &mut Context<Workspace>,
3006 ) {
3007 let project = workspace.project().clone();
3008 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3009
3010 cx.spawn_in(window, async move |workspace, cx| {
3011 let buffer = create.await?;
3012 workspace.update_in(cx, move |workspace, window, cx| {
3013 workspace.split_item(
3014 direction,
3015 Box::new(
3016 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3017 ),
3018 window,
3019 cx,
3020 )
3021 })?;
3022 anyhow::Ok(())
3023 })
3024 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3025 match e.error_code() {
3026 ErrorCode::RemoteUpgradeRequired => Some(format!(
3027 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3028 e.error_tag("required").unwrap_or("the latest version")
3029 )),
3030 _ => None,
3031 }
3032 });
3033 }
3034
3035 pub fn leader_id(&self) -> Option<CollaboratorId> {
3036 self.leader_id
3037 }
3038
3039 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3040 &self.buffer
3041 }
3042
3043 pub fn project(&self) -> Option<&Entity<Project>> {
3044 self.project.as_ref()
3045 }
3046
3047 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3048 self.workspace.as_ref()?.0.upgrade()
3049 }
3050
3051 /// Returns the workspace serialization ID if this editor should be serialized.
3052 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3053 self.workspace
3054 .as_ref()
3055 .filter(|_| self.should_serialize_buffer())
3056 .and_then(|workspace| workspace.1)
3057 }
3058
3059 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3060 self.buffer().read(cx).title(cx)
3061 }
3062
3063 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3064 let git_blame_gutter_max_author_length = self
3065 .render_git_blame_gutter(cx)
3066 .then(|| {
3067 if let Some(blame) = self.blame.as_ref() {
3068 let max_author_length =
3069 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3070 Some(max_author_length)
3071 } else {
3072 None
3073 }
3074 })
3075 .flatten();
3076
3077 EditorSnapshot {
3078 mode: self.mode.clone(),
3079 show_gutter: self.show_gutter,
3080 offset_content: self.offset_content,
3081 show_line_numbers: self.show_line_numbers,
3082 number_deleted_lines: self.number_deleted_lines,
3083 show_git_diff_gutter: self.show_git_diff_gutter,
3084 show_code_actions: self.show_code_actions,
3085 show_runnables: self.show_runnables,
3086 show_breakpoints: self.show_breakpoints,
3087 git_blame_gutter_max_author_length,
3088 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
3089 placeholder_display_snapshot: self
3090 .placeholder_display_map
3091 .as_ref()
3092 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3093 scroll_anchor: self.scroll_manager.anchor(),
3094 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3095 is_focused: self.focus_handle.is_focused(window),
3096 current_line_highlight: self
3097 .current_line_highlight
3098 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3099 gutter_hovered: self.gutter_hovered,
3100 }
3101 }
3102
3103 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3104 self.buffer.read(cx).language_at(point, cx)
3105 }
3106
3107 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3108 self.buffer.read(cx).read(cx).file_at(point).cloned()
3109 }
3110
3111 pub fn active_excerpt(
3112 &self,
3113 cx: &App,
3114 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3115 self.buffer
3116 .read(cx)
3117 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3118 }
3119
3120 pub fn mode(&self) -> &EditorMode {
3121 &self.mode
3122 }
3123
3124 pub fn set_mode(&mut self, mode: EditorMode) {
3125 self.mode = mode;
3126 }
3127
3128 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3129 self.collaboration_hub.as_deref()
3130 }
3131
3132 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3133 self.collaboration_hub = Some(hub);
3134 }
3135
3136 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3137 self.in_project_search = in_project_search;
3138 }
3139
3140 pub fn set_custom_context_menu(
3141 &mut self,
3142 f: impl 'static
3143 + Fn(
3144 &mut Self,
3145 DisplayPoint,
3146 &mut Window,
3147 &mut Context<Self>,
3148 ) -> Option<Entity<ui::ContextMenu>>,
3149 ) {
3150 self.custom_context_menu = Some(Box::new(f))
3151 }
3152
3153 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3154 self.completion_provider = provider;
3155 }
3156
3157 #[cfg(any(test, feature = "test-support"))]
3158 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3159 self.completion_provider.clone()
3160 }
3161
3162 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3163 self.semantics_provider.clone()
3164 }
3165
3166 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3167 self.semantics_provider = provider;
3168 }
3169
3170 pub fn set_edit_prediction_provider<T>(
3171 &mut self,
3172 provider: Option<Entity<T>>,
3173 window: &mut Window,
3174 cx: &mut Context<Self>,
3175 ) where
3176 T: EditPredictionDelegate,
3177 {
3178 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3179 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3180 if this.focus_handle.is_focused(window) {
3181 this.update_visible_edit_prediction(window, cx);
3182 }
3183 }),
3184 provider: Arc::new(provider),
3185 });
3186 self.update_edit_prediction_settings(cx);
3187 self.refresh_edit_prediction(false, false, window, cx);
3188 }
3189
3190 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3191 self.placeholder_display_map
3192 .as_ref()
3193 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3194 }
3195
3196 pub fn set_placeholder_text(
3197 &mut self,
3198 placeholder_text: &str,
3199 window: &mut Window,
3200 cx: &mut Context<Self>,
3201 ) {
3202 let multibuffer = cx
3203 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3204
3205 let style = window.text_style();
3206
3207 self.placeholder_display_map = Some(cx.new(|cx| {
3208 DisplayMap::new(
3209 multibuffer,
3210 style.font(),
3211 style.font_size.to_pixels(window.rem_size()),
3212 None,
3213 FILE_HEADER_HEIGHT,
3214 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3215 Default::default(),
3216 DiagnosticSeverity::Off,
3217 cx,
3218 )
3219 }));
3220 cx.notify();
3221 }
3222
3223 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3224 self.cursor_shape = cursor_shape;
3225
3226 // Disrupt blink for immediate user feedback that the cursor shape has changed
3227 self.blink_manager.update(cx, BlinkManager::show_cursor);
3228
3229 cx.notify();
3230 }
3231
3232 pub fn cursor_shape(&self) -> CursorShape {
3233 self.cursor_shape
3234 }
3235
3236 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3237 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3238 }
3239
3240 pub fn set_current_line_highlight(
3241 &mut self,
3242 current_line_highlight: Option<CurrentLineHighlight>,
3243 ) {
3244 self.current_line_highlight = current_line_highlight;
3245 }
3246
3247 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3248 self.collapse_matches = collapse_matches;
3249 }
3250
3251 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3252 if self.collapse_matches {
3253 return range.start..range.start;
3254 }
3255 range.clone()
3256 }
3257
3258 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3259 self.display_map.read(cx).clip_at_line_ends
3260 }
3261
3262 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3263 if self.display_map.read(cx).clip_at_line_ends != clip {
3264 self.display_map
3265 .update(cx, |map, _| map.clip_at_line_ends = clip);
3266 }
3267 }
3268
3269 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3270 self.input_enabled = input_enabled;
3271 }
3272
3273 pub fn set_edit_predictions_hidden_for_vim_mode(
3274 &mut self,
3275 hidden: bool,
3276 window: &mut Window,
3277 cx: &mut Context<Self>,
3278 ) {
3279 if hidden != self.edit_predictions_hidden_for_vim_mode {
3280 self.edit_predictions_hidden_for_vim_mode = hidden;
3281 if hidden {
3282 self.update_visible_edit_prediction(window, cx);
3283 } else {
3284 self.refresh_edit_prediction(true, false, window, cx);
3285 }
3286 }
3287 }
3288
3289 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3290 self.menu_edit_predictions_policy = value;
3291 }
3292
3293 pub fn set_autoindent(&mut self, autoindent: bool) {
3294 if autoindent {
3295 self.autoindent_mode = Some(AutoindentMode::EachLine);
3296 } else {
3297 self.autoindent_mode = None;
3298 }
3299 }
3300
3301 pub fn capability(&self, cx: &App) -> Capability {
3302 if self.read_only {
3303 Capability::ReadOnly
3304 } else {
3305 self.buffer.read(cx).capability()
3306 }
3307 }
3308
3309 pub fn read_only(&self, cx: &App) -> bool {
3310 self.read_only || self.buffer.read(cx).read_only()
3311 }
3312
3313 pub fn set_read_only(&mut self, read_only: bool) {
3314 self.read_only = read_only;
3315 }
3316
3317 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3318 self.use_autoclose = autoclose;
3319 }
3320
3321 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3322 self.use_auto_surround = auto_surround;
3323 }
3324
3325 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3326 self.auto_replace_emoji_shortcode = auto_replace;
3327 }
3328
3329 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3330 self.buffer_serialization = should_serialize.then(|| {
3331 BufferSerialization::new(
3332 ProjectSettings::get_global(cx)
3333 .session
3334 .restore_unsaved_buffers,
3335 )
3336 })
3337 }
3338
3339 fn should_serialize_buffer(&self) -> bool {
3340 self.buffer_serialization.is_some()
3341 }
3342
3343 pub fn toggle_edit_predictions(
3344 &mut self,
3345 _: &ToggleEditPrediction,
3346 window: &mut Window,
3347 cx: &mut Context<Self>,
3348 ) {
3349 if self.show_edit_predictions_override.is_some() {
3350 self.set_show_edit_predictions(None, window, cx);
3351 } else {
3352 let show_edit_predictions = !self.edit_predictions_enabled();
3353 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3354 }
3355 }
3356
3357 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3358 self.show_completions_on_input_override = show_completions_on_input;
3359 }
3360
3361 pub fn set_show_edit_predictions(
3362 &mut self,
3363 show_edit_predictions: Option<bool>,
3364 window: &mut Window,
3365 cx: &mut Context<Self>,
3366 ) {
3367 self.show_edit_predictions_override = show_edit_predictions;
3368 self.update_edit_prediction_settings(cx);
3369
3370 if let Some(false) = show_edit_predictions {
3371 self.discard_edit_prediction(false, cx);
3372 } else {
3373 self.refresh_edit_prediction(false, true, window, cx);
3374 }
3375 }
3376
3377 fn edit_predictions_disabled_in_scope(
3378 &self,
3379 buffer: &Entity<Buffer>,
3380 buffer_position: language::Anchor,
3381 cx: &App,
3382 ) -> bool {
3383 let snapshot = buffer.read(cx).snapshot();
3384 let settings = snapshot.settings_at(buffer_position, cx);
3385
3386 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3387 return false;
3388 };
3389
3390 scope.override_name().is_some_and(|scope_name| {
3391 settings
3392 .edit_predictions_disabled_in
3393 .iter()
3394 .any(|s| s == scope_name)
3395 })
3396 }
3397
3398 pub fn set_use_modal_editing(&mut self, to: bool) {
3399 self.use_modal_editing = to;
3400 }
3401
3402 pub fn use_modal_editing(&self) -> bool {
3403 self.use_modal_editing
3404 }
3405
3406 fn selections_did_change(
3407 &mut self,
3408 local: bool,
3409 old_cursor_position: &Anchor,
3410 effects: SelectionEffects,
3411 window: &mut Window,
3412 cx: &mut Context<Self>,
3413 ) {
3414 window.invalidate_character_coordinates();
3415
3416 // Copy selections to primary selection buffer
3417 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3418 if local {
3419 let selections = self
3420 .selections
3421 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3422 let buffer_handle = self.buffer.read(cx).read(cx);
3423
3424 let mut text = String::new();
3425 for (index, selection) in selections.iter().enumerate() {
3426 let text_for_selection = buffer_handle
3427 .text_for_range(selection.start..selection.end)
3428 .collect::<String>();
3429
3430 text.push_str(&text_for_selection);
3431 if index != selections.len() - 1 {
3432 text.push('\n');
3433 }
3434 }
3435
3436 if !text.is_empty() {
3437 cx.write_to_primary(ClipboardItem::new_string(text));
3438 }
3439 }
3440
3441 let selection_anchors = self.selections.disjoint_anchors_arc();
3442
3443 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3444 self.buffer.update(cx, |buffer, cx| {
3445 buffer.set_active_selections(
3446 &selection_anchors,
3447 self.selections.line_mode(),
3448 self.cursor_shape,
3449 cx,
3450 )
3451 });
3452 }
3453 let display_map = self
3454 .display_map
3455 .update(cx, |display_map, cx| display_map.snapshot(cx));
3456 let buffer = display_map.buffer_snapshot();
3457 if self.selections.count() == 1 {
3458 self.add_selections_state = None;
3459 }
3460 self.select_next_state = None;
3461 self.select_prev_state = None;
3462 self.select_syntax_node_history.try_clear();
3463 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3464 self.snippet_stack.invalidate(&selection_anchors, buffer);
3465 self.take_rename(false, window, cx);
3466
3467 let newest_selection = self.selections.newest_anchor();
3468 let new_cursor_position = newest_selection.head();
3469 let selection_start = newest_selection.start;
3470
3471 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3472 self.push_to_nav_history(
3473 *old_cursor_position,
3474 Some(new_cursor_position.to_point(buffer)),
3475 false,
3476 effects.nav_history == Some(true),
3477 cx,
3478 );
3479 }
3480
3481 if local {
3482 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3483 self.register_buffer(buffer_id, cx);
3484 }
3485
3486 let mut context_menu = self.context_menu.borrow_mut();
3487 let completion_menu = match context_menu.as_ref() {
3488 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3489 Some(CodeContextMenu::CodeActions(_)) => {
3490 *context_menu = None;
3491 None
3492 }
3493 None => None,
3494 };
3495 let completion_position = completion_menu.map(|menu| menu.initial_position);
3496 drop(context_menu);
3497
3498 if effects.completions
3499 && let Some(completion_position) = completion_position
3500 {
3501 let start_offset = selection_start.to_offset(buffer);
3502 let position_matches = start_offset == completion_position.to_offset(buffer);
3503 let continue_showing = if let Some((snap, ..)) =
3504 buffer.point_to_buffer_offset(completion_position)
3505 && !snap.capability.editable()
3506 {
3507 false
3508 } else if position_matches {
3509 if self.snippet_stack.is_empty() {
3510 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3511 == Some(CharKind::Word)
3512 } else {
3513 // Snippet choices can be shown even when the cursor is in whitespace.
3514 // Dismissing the menu with actions like backspace is handled by
3515 // invalidation regions.
3516 true
3517 }
3518 } else {
3519 false
3520 };
3521
3522 if continue_showing {
3523 self.open_or_update_completions_menu(None, None, false, window, cx);
3524 } else {
3525 self.hide_context_menu(window, cx);
3526 }
3527 }
3528
3529 hide_hover(self, cx);
3530
3531 if old_cursor_position.to_display_point(&display_map).row()
3532 != new_cursor_position.to_display_point(&display_map).row()
3533 {
3534 self.available_code_actions.take();
3535 }
3536 self.refresh_code_actions(window, cx);
3537 self.refresh_document_highlights(cx);
3538 refresh_linked_ranges(self, window, cx);
3539
3540 self.refresh_selected_text_highlights(false, window, cx);
3541 self.refresh_matching_bracket_highlights(window, cx);
3542 self.update_visible_edit_prediction(window, cx);
3543 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3544 self.inline_blame_popover.take();
3545 if self.git_blame_inline_enabled {
3546 self.start_inline_blame_timer(window, cx);
3547 }
3548 }
3549
3550 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3551
3552 if local && !self.suppress_selection_callback {
3553 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3554 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3555 callback(cursor_position, window, cx);
3556 }
3557 }
3558
3559 cx.emit(EditorEvent::SelectionsChanged { local });
3560
3561 let selections = &self.selections.disjoint_anchors_arc();
3562 if selections.len() == 1 {
3563 cx.emit(SearchEvent::ActiveMatchChanged)
3564 }
3565 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3566 let inmemory_selections = selections
3567 .iter()
3568 .map(|s| {
3569 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3570 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3571 })
3572 .collect();
3573 self.update_restoration_data(cx, |data| {
3574 data.selections = inmemory_selections;
3575 });
3576
3577 if WorkspaceSettings::get(None, cx).restore_on_startup
3578 != RestoreOnStartupBehavior::EmptyTab
3579 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3580 {
3581 let snapshot = self.buffer().read(cx).snapshot(cx);
3582 let selections = selections.clone();
3583 let background_executor = cx.background_executor().clone();
3584 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3585 self.serialize_selections = cx.background_spawn(async move {
3586 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3587 let db_selections = selections
3588 .iter()
3589 .map(|selection| {
3590 (
3591 selection.start.to_offset(&snapshot).0,
3592 selection.end.to_offset(&snapshot).0,
3593 )
3594 })
3595 .collect();
3596
3597 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3598 .await
3599 .with_context(|| {
3600 format!(
3601 "persisting editor selections for editor {editor_id}, \
3602 workspace {workspace_id:?}"
3603 )
3604 })
3605 .log_err();
3606 });
3607 }
3608 }
3609
3610 cx.notify();
3611 }
3612
3613 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3614 use text::ToOffset as _;
3615 use text::ToPoint as _;
3616
3617 if self.mode.is_minimap()
3618 || WorkspaceSettings::get(None, cx).restore_on_startup
3619 == RestoreOnStartupBehavior::EmptyTab
3620 {
3621 return;
3622 }
3623
3624 if !self.buffer().read(cx).is_singleton() {
3625 return;
3626 }
3627
3628 let display_snapshot = self
3629 .display_map
3630 .update(cx, |display_map, cx| display_map.snapshot(cx));
3631 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3632 return;
3633 };
3634 let inmemory_folds = display_snapshot
3635 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3636 .map(|fold| {
3637 fold.range.start.text_anchor.to_point(&snapshot)
3638 ..fold.range.end.text_anchor.to_point(&snapshot)
3639 })
3640 .collect();
3641 self.update_restoration_data(cx, |data| {
3642 data.folds = inmemory_folds;
3643 });
3644
3645 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3646 return;
3647 };
3648 let background_executor = cx.background_executor().clone();
3649 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3650 const FINGERPRINT_LEN: usize = 32;
3651 let db_folds = display_snapshot
3652 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3653 .map(|fold| {
3654 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3655 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3656
3657 // Extract fingerprints - content at fold boundaries for validation on restore
3658 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3659 // content that might change independently.
3660 // start_fp: first min(32, fold_len) bytes of fold content
3661 // end_fp: last min(32, fold_len) bytes of fold content
3662 // Clip to character boundaries to handle multibyte UTF-8 characters.
3663 let fold_len = end - start;
3664 let start_fp_end = snapshot
3665 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3666 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3667 let end_fp_start = snapshot
3668 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3669 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3670
3671 (start, end, start_fp, end_fp)
3672 })
3673 .collect::<Vec<_>>();
3674 self.serialize_folds = cx.background_spawn(async move {
3675 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3676 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3677 .await
3678 .with_context(|| {
3679 format!(
3680 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3681 )
3682 })
3683 .log_err();
3684 });
3685 }
3686
3687 pub fn sync_selections(
3688 &mut self,
3689 other: Entity<Editor>,
3690 cx: &mut Context<Self>,
3691 ) -> gpui::Subscription {
3692 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3693 if !other_selections.is_empty() {
3694 self.selections
3695 .change_with(&self.display_snapshot(cx), |selections| {
3696 selections.select_anchors(other_selections);
3697 });
3698 }
3699
3700 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3701 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3702 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3703 if other_selections.is_empty() {
3704 return;
3705 }
3706 let snapshot = this.display_snapshot(cx);
3707 this.selections.change_with(&snapshot, |selections| {
3708 selections.select_anchors(other_selections);
3709 });
3710 }
3711 });
3712
3713 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3714 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3715 let these_selections = this.selections.disjoint_anchors().to_vec();
3716 if these_selections.is_empty() {
3717 return;
3718 }
3719 other.update(cx, |other_editor, cx| {
3720 let snapshot = other_editor.display_snapshot(cx);
3721 other_editor
3722 .selections
3723 .change_with(&snapshot, |selections| {
3724 selections.select_anchors(these_selections);
3725 })
3726 });
3727 }
3728 });
3729
3730 Subscription::join(other_subscription, this_subscription)
3731 }
3732
3733 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3734 if self.buffer().read(cx).is_singleton() {
3735 return;
3736 }
3737 let snapshot = self.buffer.read(cx).snapshot(cx);
3738 let buffer_ids: HashSet<BufferId> = self
3739 .selections
3740 .disjoint_anchor_ranges()
3741 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3742 .collect();
3743 for buffer_id in buffer_ids {
3744 self.unfold_buffer(buffer_id, cx);
3745 }
3746 }
3747
3748 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3749 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3750 /// effects of selection change occur at the end of the transaction.
3751 pub fn change_selections<R>(
3752 &mut self,
3753 effects: SelectionEffects,
3754 window: &mut Window,
3755 cx: &mut Context<Self>,
3756 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3757 ) -> R {
3758 let snapshot = self.display_snapshot(cx);
3759 if let Some(state) = &mut self.deferred_selection_effects_state {
3760 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3761 state.effects.completions = effects.completions;
3762 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3763 let (changed, result) = self.selections.change_with(&snapshot, change);
3764 state.changed |= changed;
3765 return result;
3766 }
3767 let mut state = DeferredSelectionEffectsState {
3768 changed: false,
3769 effects,
3770 old_cursor_position: self.selections.newest_anchor().head(),
3771 history_entry: SelectionHistoryEntry {
3772 selections: self.selections.disjoint_anchors_arc(),
3773 select_next_state: self.select_next_state.clone(),
3774 select_prev_state: self.select_prev_state.clone(),
3775 add_selections_state: self.add_selections_state.clone(),
3776 },
3777 };
3778 let (changed, result) = self.selections.change_with(&snapshot, change);
3779 state.changed = state.changed || changed;
3780 if self.defer_selection_effects {
3781 self.deferred_selection_effects_state = Some(state);
3782 } else {
3783 self.apply_selection_effects(state, window, cx);
3784 }
3785 result
3786 }
3787
3788 /// Defers the effects of selection change, so that the effects of multiple calls to
3789 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3790 /// to selection history and the state of popovers based on selection position aren't
3791 /// erroneously updated.
3792 pub fn with_selection_effects_deferred<R>(
3793 &mut self,
3794 window: &mut Window,
3795 cx: &mut Context<Self>,
3796 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3797 ) -> R {
3798 let already_deferred = self.defer_selection_effects;
3799 self.defer_selection_effects = true;
3800 let result = update(self, window, cx);
3801 if !already_deferred {
3802 self.defer_selection_effects = false;
3803 if let Some(state) = self.deferred_selection_effects_state.take() {
3804 self.apply_selection_effects(state, window, cx);
3805 }
3806 }
3807 result
3808 }
3809
3810 fn apply_selection_effects(
3811 &mut self,
3812 state: DeferredSelectionEffectsState,
3813 window: &mut Window,
3814 cx: &mut Context<Self>,
3815 ) {
3816 if state.changed {
3817 self.selection_history.push(state.history_entry);
3818
3819 if let Some(autoscroll) = state.effects.scroll {
3820 self.request_autoscroll(autoscroll, cx);
3821 }
3822
3823 let old_cursor_position = &state.old_cursor_position;
3824
3825 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3826
3827 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3828 self.show_signature_help_auto(window, cx);
3829 }
3830 }
3831 }
3832
3833 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3834 where
3835 I: IntoIterator<Item = (Range<S>, T)>,
3836 S: ToOffset,
3837 T: Into<Arc<str>>,
3838 {
3839 if self.read_only(cx) {
3840 return;
3841 }
3842
3843 self.buffer
3844 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3845 }
3846
3847 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3848 where
3849 I: IntoIterator<Item = (Range<S>, T)>,
3850 S: ToOffset,
3851 T: Into<Arc<str>>,
3852 {
3853 if self.read_only(cx) {
3854 return;
3855 }
3856
3857 self.buffer.update(cx, |buffer, cx| {
3858 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3859 });
3860 }
3861
3862 pub fn edit_with_block_indent<I, S, T>(
3863 &mut self,
3864 edits: I,
3865 original_indent_columns: Vec<Option<u32>>,
3866 cx: &mut Context<Self>,
3867 ) where
3868 I: IntoIterator<Item = (Range<S>, T)>,
3869 S: ToOffset,
3870 T: Into<Arc<str>>,
3871 {
3872 if self.read_only(cx) {
3873 return;
3874 }
3875
3876 self.buffer.update(cx, |buffer, cx| {
3877 buffer.edit(
3878 edits,
3879 Some(AutoindentMode::Block {
3880 original_indent_columns,
3881 }),
3882 cx,
3883 )
3884 });
3885 }
3886
3887 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3888 self.hide_context_menu(window, cx);
3889
3890 match phase {
3891 SelectPhase::Begin {
3892 position,
3893 add,
3894 click_count,
3895 } => self.begin_selection(position, add, click_count, window, cx),
3896 SelectPhase::BeginColumnar {
3897 position,
3898 goal_column,
3899 reset,
3900 mode,
3901 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3902 SelectPhase::Extend {
3903 position,
3904 click_count,
3905 } => self.extend_selection(position, click_count, window, cx),
3906 SelectPhase::Update {
3907 position,
3908 goal_column,
3909 scroll_delta,
3910 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3911 SelectPhase::End => self.end_selection(window, cx),
3912 }
3913 }
3914
3915 fn extend_selection(
3916 &mut self,
3917 position: DisplayPoint,
3918 click_count: usize,
3919 window: &mut Window,
3920 cx: &mut Context<Self>,
3921 ) {
3922 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3923 let tail = self
3924 .selections
3925 .newest::<MultiBufferOffset>(&display_map)
3926 .tail();
3927 let click_count = click_count.max(match self.selections.select_mode() {
3928 SelectMode::Character => 1,
3929 SelectMode::Word(_) => 2,
3930 SelectMode::Line(_) => 3,
3931 SelectMode::All => 4,
3932 });
3933 self.begin_selection(position, false, click_count, window, cx);
3934
3935 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3936
3937 let current_selection = match self.selections.select_mode() {
3938 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3939 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3940 };
3941
3942 let mut pending_selection = self
3943 .selections
3944 .pending_anchor()
3945 .cloned()
3946 .expect("extend_selection not called with pending selection");
3947
3948 if pending_selection
3949 .start
3950 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3951 == Ordering::Greater
3952 {
3953 pending_selection.start = current_selection.start;
3954 }
3955 if pending_selection
3956 .end
3957 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3958 == Ordering::Less
3959 {
3960 pending_selection.end = current_selection.end;
3961 pending_selection.reversed = true;
3962 }
3963
3964 let mut pending_mode = self.selections.pending_mode().unwrap();
3965 match &mut pending_mode {
3966 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3967 _ => {}
3968 }
3969
3970 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3971 SelectionEffects::scroll(Autoscroll::fit())
3972 } else {
3973 SelectionEffects::no_scroll()
3974 };
3975
3976 self.change_selections(effects, window, cx, |s| {
3977 s.set_pending(pending_selection.clone(), pending_mode);
3978 s.set_is_extending(true);
3979 });
3980 }
3981
3982 fn begin_selection(
3983 &mut self,
3984 position: DisplayPoint,
3985 add: bool,
3986 click_count: usize,
3987 window: &mut Window,
3988 cx: &mut Context<Self>,
3989 ) {
3990 if !self.focus_handle.is_focused(window) {
3991 self.last_focused_descendant = None;
3992 window.focus(&self.focus_handle, cx);
3993 }
3994
3995 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3996 let buffer = display_map.buffer_snapshot();
3997 let position = display_map.clip_point(position, Bias::Left);
3998
3999 let start;
4000 let end;
4001 let mode;
4002 let mut auto_scroll;
4003 match click_count {
4004 1 => {
4005 start = buffer.anchor_before(position.to_point(&display_map));
4006 end = start;
4007 mode = SelectMode::Character;
4008 auto_scroll = true;
4009 }
4010 2 => {
4011 let position = display_map
4012 .clip_point(position, Bias::Left)
4013 .to_offset(&display_map, Bias::Left);
4014 let (range, _) = buffer.surrounding_word(position, None);
4015 start = buffer.anchor_before(range.start);
4016 end = buffer.anchor_before(range.end);
4017 mode = SelectMode::Word(start..end);
4018 auto_scroll = true;
4019 }
4020 3 => {
4021 let position = display_map
4022 .clip_point(position, Bias::Left)
4023 .to_point(&display_map);
4024 let line_start = display_map.prev_line_boundary(position).0;
4025 let next_line_start = buffer.clip_point(
4026 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4027 Bias::Left,
4028 );
4029 start = buffer.anchor_before(line_start);
4030 end = buffer.anchor_before(next_line_start);
4031 mode = SelectMode::Line(start..end);
4032 auto_scroll = true;
4033 }
4034 _ => {
4035 start = buffer.anchor_before(MultiBufferOffset(0));
4036 end = buffer.anchor_before(buffer.len());
4037 mode = SelectMode::All;
4038 auto_scroll = false;
4039 }
4040 }
4041 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4042
4043 let point_to_delete: Option<usize> = {
4044 let selected_points: Vec<Selection<Point>> =
4045 self.selections.disjoint_in_range(start..end, &display_map);
4046
4047 if !add || click_count > 1 {
4048 None
4049 } else if !selected_points.is_empty() {
4050 Some(selected_points[0].id)
4051 } else {
4052 let clicked_point_already_selected =
4053 self.selections.disjoint_anchors().iter().find(|selection| {
4054 selection.start.to_point(buffer) == start.to_point(buffer)
4055 || selection.end.to_point(buffer) == end.to_point(buffer)
4056 });
4057
4058 clicked_point_already_selected.map(|selection| selection.id)
4059 }
4060 };
4061
4062 let selections_count = self.selections.count();
4063 let effects = if auto_scroll {
4064 SelectionEffects::default()
4065 } else {
4066 SelectionEffects::no_scroll()
4067 };
4068
4069 self.change_selections(effects, window, cx, |s| {
4070 if let Some(point_to_delete) = point_to_delete {
4071 s.delete(point_to_delete);
4072
4073 if selections_count == 1 {
4074 s.set_pending_anchor_range(start..end, mode);
4075 }
4076 } else {
4077 if !add {
4078 s.clear_disjoint();
4079 }
4080
4081 s.set_pending_anchor_range(start..end, mode);
4082 }
4083 });
4084 }
4085
4086 fn begin_columnar_selection(
4087 &mut self,
4088 position: DisplayPoint,
4089 goal_column: u32,
4090 reset: bool,
4091 mode: ColumnarMode,
4092 window: &mut Window,
4093 cx: &mut Context<Self>,
4094 ) {
4095 if !self.focus_handle.is_focused(window) {
4096 self.last_focused_descendant = None;
4097 window.focus(&self.focus_handle, cx);
4098 }
4099
4100 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4101
4102 if reset {
4103 let pointer_position = display_map
4104 .buffer_snapshot()
4105 .anchor_before(position.to_point(&display_map));
4106
4107 self.change_selections(
4108 SelectionEffects::scroll(Autoscroll::newest()),
4109 window,
4110 cx,
4111 |s| {
4112 s.clear_disjoint();
4113 s.set_pending_anchor_range(
4114 pointer_position..pointer_position,
4115 SelectMode::Character,
4116 );
4117 },
4118 );
4119 };
4120
4121 let tail = self.selections.newest::<Point>(&display_map).tail();
4122 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4123 self.columnar_selection_state = match mode {
4124 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4125 selection_tail: selection_anchor,
4126 display_point: if reset {
4127 if position.column() != goal_column {
4128 Some(DisplayPoint::new(position.row(), goal_column))
4129 } else {
4130 None
4131 }
4132 } else {
4133 None
4134 },
4135 }),
4136 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4137 selection_tail: selection_anchor,
4138 }),
4139 };
4140
4141 if !reset {
4142 self.select_columns(position, goal_column, &display_map, window, cx);
4143 }
4144 }
4145
4146 fn update_selection(
4147 &mut self,
4148 position: DisplayPoint,
4149 goal_column: u32,
4150 scroll_delta: gpui::Point<f32>,
4151 window: &mut Window,
4152 cx: &mut Context<Self>,
4153 ) {
4154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4155
4156 if self.columnar_selection_state.is_some() {
4157 self.select_columns(position, goal_column, &display_map, window, cx);
4158 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4159 let buffer = display_map.buffer_snapshot();
4160 let head;
4161 let tail;
4162 let mode = self.selections.pending_mode().unwrap();
4163 match &mode {
4164 SelectMode::Character => {
4165 head = position.to_point(&display_map);
4166 tail = pending.tail().to_point(buffer);
4167 }
4168 SelectMode::Word(original_range) => {
4169 let offset = display_map
4170 .clip_point(position, Bias::Left)
4171 .to_offset(&display_map, Bias::Left);
4172 let original_range = original_range.to_offset(buffer);
4173
4174 let head_offset = if buffer.is_inside_word(offset, None)
4175 || original_range.contains(&offset)
4176 {
4177 let (word_range, _) = buffer.surrounding_word(offset, None);
4178 if word_range.start < original_range.start {
4179 word_range.start
4180 } else {
4181 word_range.end
4182 }
4183 } else {
4184 offset
4185 };
4186
4187 head = head_offset.to_point(buffer);
4188 if head_offset <= original_range.start {
4189 tail = original_range.end.to_point(buffer);
4190 } else {
4191 tail = original_range.start.to_point(buffer);
4192 }
4193 }
4194 SelectMode::Line(original_range) => {
4195 let original_range = original_range.to_point(display_map.buffer_snapshot());
4196
4197 let position = display_map
4198 .clip_point(position, Bias::Left)
4199 .to_point(&display_map);
4200 let line_start = display_map.prev_line_boundary(position).0;
4201 let next_line_start = buffer.clip_point(
4202 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4203 Bias::Left,
4204 );
4205
4206 if line_start < original_range.start {
4207 head = line_start
4208 } else {
4209 head = next_line_start
4210 }
4211
4212 if head <= original_range.start {
4213 tail = original_range.end;
4214 } else {
4215 tail = original_range.start;
4216 }
4217 }
4218 SelectMode::All => {
4219 return;
4220 }
4221 };
4222
4223 if head < tail {
4224 pending.start = buffer.anchor_before(head);
4225 pending.end = buffer.anchor_before(tail);
4226 pending.reversed = true;
4227 } else {
4228 pending.start = buffer.anchor_before(tail);
4229 pending.end = buffer.anchor_before(head);
4230 pending.reversed = false;
4231 }
4232
4233 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4234 s.set_pending(pending.clone(), mode);
4235 });
4236 } else {
4237 log::error!("update_selection dispatched with no pending selection");
4238 return;
4239 }
4240
4241 self.apply_scroll_delta(scroll_delta, window, cx);
4242 cx.notify();
4243 }
4244
4245 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4246 self.columnar_selection_state.take();
4247 if let Some(pending_mode) = self.selections.pending_mode() {
4248 let selections = self
4249 .selections
4250 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4251 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4252 s.select(selections);
4253 s.clear_pending();
4254 if s.is_extending() {
4255 s.set_is_extending(false);
4256 } else {
4257 s.set_select_mode(pending_mode);
4258 }
4259 });
4260 }
4261 }
4262
4263 fn select_columns(
4264 &mut self,
4265 head: DisplayPoint,
4266 goal_column: u32,
4267 display_map: &DisplaySnapshot,
4268 window: &mut Window,
4269 cx: &mut Context<Self>,
4270 ) {
4271 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4272 return;
4273 };
4274
4275 let tail = match columnar_state {
4276 ColumnarSelectionState::FromMouse {
4277 selection_tail,
4278 display_point,
4279 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4280 ColumnarSelectionState::FromSelection { selection_tail } => {
4281 selection_tail.to_display_point(display_map)
4282 }
4283 };
4284
4285 let start_row = cmp::min(tail.row(), head.row());
4286 let end_row = cmp::max(tail.row(), head.row());
4287 let start_column = cmp::min(tail.column(), goal_column);
4288 let end_column = cmp::max(tail.column(), goal_column);
4289 let reversed = start_column < tail.column();
4290
4291 let selection_ranges = (start_row.0..=end_row.0)
4292 .map(DisplayRow)
4293 .filter_map(|row| {
4294 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4295 || start_column <= display_map.line_len(row))
4296 && !display_map.is_block_line(row)
4297 {
4298 let start = display_map
4299 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4300 .to_point(display_map);
4301 let end = display_map
4302 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4303 .to_point(display_map);
4304 if reversed {
4305 Some(end..start)
4306 } else {
4307 Some(start..end)
4308 }
4309 } else {
4310 None
4311 }
4312 })
4313 .collect::<Vec<_>>();
4314 if selection_ranges.is_empty() {
4315 return;
4316 }
4317
4318 let ranges = match columnar_state {
4319 ColumnarSelectionState::FromMouse { .. } => {
4320 let mut non_empty_ranges = selection_ranges
4321 .iter()
4322 .filter(|selection_range| selection_range.start != selection_range.end)
4323 .peekable();
4324 if non_empty_ranges.peek().is_some() {
4325 non_empty_ranges.cloned().collect()
4326 } else {
4327 selection_ranges
4328 }
4329 }
4330 _ => selection_ranges,
4331 };
4332
4333 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4334 s.select_ranges(ranges);
4335 });
4336 cx.notify();
4337 }
4338
4339 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4340 self.selections
4341 .all_adjusted(snapshot)
4342 .iter()
4343 .any(|selection| !selection.is_empty())
4344 }
4345
4346 pub fn has_pending_nonempty_selection(&self) -> bool {
4347 let pending_nonempty_selection = match self.selections.pending_anchor() {
4348 Some(Selection { start, end, .. }) => start != end,
4349 None => false,
4350 };
4351
4352 pending_nonempty_selection
4353 || (self.columnar_selection_state.is_some()
4354 && self.selections.disjoint_anchors().len() > 1)
4355 }
4356
4357 pub fn has_pending_selection(&self) -> bool {
4358 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4359 }
4360
4361 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4362 self.selection_mark_mode = false;
4363 self.selection_drag_state = SelectionDragState::None;
4364
4365 if self.dismiss_menus_and_popups(true, window, cx) {
4366 cx.notify();
4367 return;
4368 }
4369 if self.clear_expanded_diff_hunks(cx) {
4370 cx.notify();
4371 return;
4372 }
4373 if self.show_git_blame_gutter {
4374 self.show_git_blame_gutter = false;
4375 cx.notify();
4376 return;
4377 }
4378
4379 if self.mode.is_full()
4380 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4381 {
4382 cx.notify();
4383 return;
4384 }
4385
4386 cx.propagate();
4387 }
4388
4389 pub fn dismiss_menus_and_popups(
4390 &mut self,
4391 is_user_requested: bool,
4392 window: &mut Window,
4393 cx: &mut Context<Self>,
4394 ) -> bool {
4395 let mut dismissed = false;
4396
4397 dismissed |= self.take_rename(false, window, cx).is_some();
4398 dismissed |= self.hide_blame_popover(true, cx);
4399 dismissed |= hide_hover(self, cx);
4400 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4401 dismissed |= self.hide_context_menu(window, cx).is_some();
4402 dismissed |= self.mouse_context_menu.take().is_some();
4403 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4404 dismissed |= self.snippet_stack.pop().is_some();
4405 if self.diff_review_drag_state.is_some() {
4406 self.cancel_diff_review_drag(cx);
4407 dismissed = true;
4408 }
4409 if !self.diff_review_overlays.is_empty() {
4410 self.dismiss_all_diff_review_overlays(cx);
4411 dismissed = true;
4412 }
4413
4414 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4415 self.dismiss_diagnostics(cx);
4416 dismissed = true;
4417 }
4418
4419 dismissed
4420 }
4421
4422 fn linked_editing_ranges_for(
4423 &self,
4424 selection: Range<text::Anchor>,
4425 cx: &App,
4426 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4427 if self.linked_edit_ranges.is_empty() {
4428 return None;
4429 }
4430 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4431 selection.end.buffer_id.and_then(|end_buffer_id| {
4432 if selection.start.buffer_id != Some(end_buffer_id) {
4433 return None;
4434 }
4435 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4436 let snapshot = buffer.read(cx).snapshot();
4437 self.linked_edit_ranges
4438 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4439 .map(|ranges| (ranges, snapshot, buffer))
4440 })?;
4441 use text::ToOffset as TO;
4442 // find offset from the start of current range to current cursor position
4443 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4444
4445 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4446 let start_difference = start_offset - start_byte_offset;
4447 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4448 let end_difference = end_offset - start_byte_offset;
4449 // Current range has associated linked ranges.
4450 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4451 for range in linked_ranges.iter() {
4452 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4453 let end_offset = start_offset + end_difference;
4454 let start_offset = start_offset + start_difference;
4455 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4456 continue;
4457 }
4458 if self.selections.disjoint_anchor_ranges().any(|s| {
4459 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4460 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4461 {
4462 return false;
4463 }
4464 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4465 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4466 }) {
4467 continue;
4468 }
4469 let start = buffer_snapshot.anchor_after(start_offset);
4470 let end = buffer_snapshot.anchor_after(end_offset);
4471 linked_edits
4472 .entry(buffer.clone())
4473 .or_default()
4474 .push(start..end);
4475 }
4476 Some(linked_edits)
4477 }
4478
4479 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4480 let text: Arc<str> = text.into();
4481
4482 if self.read_only(cx) {
4483 return;
4484 }
4485
4486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4487
4488 self.unfold_buffers_with_selections(cx);
4489
4490 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4491 let mut bracket_inserted = false;
4492 let mut edits = Vec::new();
4493 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4494 let mut new_selections = Vec::with_capacity(selections.len());
4495 let mut new_autoclose_regions = Vec::new();
4496 let snapshot = self.buffer.read(cx).read(cx);
4497 let mut clear_linked_edit_ranges = false;
4498 let mut all_selections_read_only = true;
4499 let mut has_adjacent_edits = false;
4500 let mut in_adjacent_group = false;
4501
4502 let mut regions = self
4503 .selections_with_autoclose_regions(selections, &snapshot)
4504 .peekable();
4505
4506 while let Some((selection, autoclose_region)) = regions.next() {
4507 if snapshot
4508 .point_to_buffer_point(selection.head())
4509 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4510 {
4511 continue;
4512 }
4513 if snapshot
4514 .point_to_buffer_point(selection.tail())
4515 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4516 {
4517 // note, ideally we'd clip the tail to the closest writeable region towards the head
4518 continue;
4519 }
4520 all_selections_read_only = false;
4521
4522 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4523 // Determine if the inserted text matches the opening or closing
4524 // bracket of any of this language's bracket pairs.
4525 let mut bracket_pair = None;
4526 let mut is_bracket_pair_start = false;
4527 let mut is_bracket_pair_end = false;
4528 if !text.is_empty() {
4529 let mut bracket_pair_matching_end = None;
4530 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4531 // and they are removing the character that triggered IME popup.
4532 for (pair, enabled) in scope.brackets() {
4533 if !pair.close && !pair.surround {
4534 continue;
4535 }
4536
4537 if enabled && pair.start.ends_with(text.as_ref()) {
4538 let prefix_len = pair.start.len() - text.len();
4539 let preceding_text_matches_prefix = prefix_len == 0
4540 || (selection.start.column >= (prefix_len as u32)
4541 && snapshot.contains_str_at(
4542 Point::new(
4543 selection.start.row,
4544 selection.start.column - (prefix_len as u32),
4545 ),
4546 &pair.start[..prefix_len],
4547 ));
4548 if preceding_text_matches_prefix {
4549 bracket_pair = Some(pair.clone());
4550 is_bracket_pair_start = true;
4551 break;
4552 }
4553 }
4554 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4555 {
4556 // take first bracket pair matching end, but don't break in case a later bracket
4557 // pair matches start
4558 bracket_pair_matching_end = Some(pair.clone());
4559 }
4560 }
4561 if let Some(end) = bracket_pair_matching_end
4562 && bracket_pair.is_none()
4563 {
4564 bracket_pair = Some(end);
4565 is_bracket_pair_end = true;
4566 }
4567 }
4568
4569 if let Some(bracket_pair) = bracket_pair {
4570 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4571 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4572 let auto_surround =
4573 self.use_auto_surround && snapshot_settings.use_auto_surround;
4574 if selection.is_empty() {
4575 if is_bracket_pair_start {
4576 // If the inserted text is a suffix of an opening bracket and the
4577 // selection is preceded by the rest of the opening bracket, then
4578 // insert the closing bracket.
4579 let following_text_allows_autoclose = snapshot
4580 .chars_at(selection.start)
4581 .next()
4582 .is_none_or(|c| scope.should_autoclose_before(c));
4583
4584 let preceding_text_allows_autoclose = selection.start.column == 0
4585 || snapshot
4586 .reversed_chars_at(selection.start)
4587 .next()
4588 .is_none_or(|c| {
4589 bracket_pair.start != bracket_pair.end
4590 || !snapshot
4591 .char_classifier_at(selection.start)
4592 .is_word(c)
4593 });
4594
4595 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4596 && bracket_pair.start.len() == 1
4597 {
4598 let target = bracket_pair.start.chars().next().unwrap();
4599 let mut byte_offset = 0u32;
4600 let current_line_count = snapshot
4601 .reversed_chars_at(selection.start)
4602 .take_while(|&c| c != '\n')
4603 .filter(|c| {
4604 byte_offset += c.len_utf8() as u32;
4605 if *c != target {
4606 return false;
4607 }
4608
4609 let point = Point::new(
4610 selection.start.row,
4611 selection.start.column.saturating_sub(byte_offset),
4612 );
4613
4614 let is_enabled = snapshot
4615 .language_scope_at(point)
4616 .and_then(|scope| {
4617 scope
4618 .brackets()
4619 .find(|(pair, _)| {
4620 pair.start == bracket_pair.start
4621 })
4622 .map(|(_, enabled)| enabled)
4623 })
4624 .unwrap_or(true);
4625
4626 let is_delimiter = snapshot
4627 .language_scope_at(Point::new(
4628 point.row,
4629 point.column + 1,
4630 ))
4631 .and_then(|scope| {
4632 scope
4633 .brackets()
4634 .find(|(pair, _)| {
4635 pair.start == bracket_pair.start
4636 })
4637 .map(|(_, enabled)| !enabled)
4638 })
4639 .unwrap_or(false);
4640
4641 is_enabled && !is_delimiter
4642 })
4643 .count();
4644 current_line_count % 2 == 1
4645 } else {
4646 false
4647 };
4648
4649 if autoclose
4650 && bracket_pair.close
4651 && following_text_allows_autoclose
4652 && preceding_text_allows_autoclose
4653 && !is_closing_quote
4654 {
4655 let anchor = snapshot.anchor_before(selection.end);
4656 new_selections.push((selection.map(|_| anchor), text.len()));
4657 new_autoclose_regions.push((
4658 anchor,
4659 text.len(),
4660 selection.id,
4661 bracket_pair.clone(),
4662 ));
4663 edits.push((
4664 selection.range(),
4665 format!("{}{}", text, bracket_pair.end).into(),
4666 ));
4667 bracket_inserted = true;
4668 continue;
4669 }
4670 }
4671
4672 if let Some(region) = autoclose_region {
4673 // If the selection is followed by an auto-inserted closing bracket,
4674 // then don't insert that closing bracket again; just move the selection
4675 // past the closing bracket.
4676 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4677 && text.as_ref() == region.pair.end.as_str()
4678 && snapshot.contains_str_at(region.range.end, text.as_ref());
4679 if should_skip {
4680 let anchor = snapshot.anchor_after(selection.end);
4681 new_selections
4682 .push((selection.map(|_| anchor), region.pair.end.len()));
4683 continue;
4684 }
4685 }
4686
4687 let always_treat_brackets_as_autoclosed = snapshot
4688 .language_settings_at(selection.start, cx)
4689 .always_treat_brackets_as_autoclosed;
4690 if always_treat_brackets_as_autoclosed
4691 && is_bracket_pair_end
4692 && snapshot.contains_str_at(selection.end, text.as_ref())
4693 {
4694 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4695 // and the inserted text is a closing bracket and the selection is followed
4696 // by the closing bracket then move the selection past the closing bracket.
4697 let anchor = snapshot.anchor_after(selection.end);
4698 new_selections.push((selection.map(|_| anchor), text.len()));
4699 continue;
4700 }
4701 }
4702 // If an opening bracket is 1 character long and is typed while
4703 // text is selected, then surround that text with the bracket pair.
4704 else if auto_surround
4705 && bracket_pair.surround
4706 && is_bracket_pair_start
4707 && bracket_pair.start.chars().count() == 1
4708 {
4709 edits.push((selection.start..selection.start, text.clone()));
4710 edits.push((
4711 selection.end..selection.end,
4712 bracket_pair.end.as_str().into(),
4713 ));
4714 bracket_inserted = true;
4715 new_selections.push((
4716 Selection {
4717 id: selection.id,
4718 start: snapshot.anchor_after(selection.start),
4719 end: snapshot.anchor_before(selection.end),
4720 reversed: selection.reversed,
4721 goal: selection.goal,
4722 },
4723 0,
4724 ));
4725 continue;
4726 }
4727 }
4728 }
4729
4730 if self.auto_replace_emoji_shortcode
4731 && selection.is_empty()
4732 && text.as_ref().ends_with(':')
4733 && let Some(possible_emoji_short_code) =
4734 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4735 && !possible_emoji_short_code.is_empty()
4736 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4737 {
4738 let emoji_shortcode_start = Point::new(
4739 selection.start.row,
4740 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4741 );
4742
4743 // Remove shortcode from buffer
4744 edits.push((
4745 emoji_shortcode_start..selection.start,
4746 "".to_string().into(),
4747 ));
4748 new_selections.push((
4749 Selection {
4750 id: selection.id,
4751 start: snapshot.anchor_after(emoji_shortcode_start),
4752 end: snapshot.anchor_before(selection.start),
4753 reversed: selection.reversed,
4754 goal: selection.goal,
4755 },
4756 0,
4757 ));
4758
4759 // Insert emoji
4760 let selection_start_anchor = snapshot.anchor_after(selection.start);
4761 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4762 edits.push((selection.start..selection.end, emoji.to_string().into()));
4763
4764 continue;
4765 }
4766
4767 let next_is_adjacent = regions
4768 .peek()
4769 .is_some_and(|(next, _)| selection.end == next.start);
4770
4771 // If not handling any auto-close operation, then just replace the selected
4772 // text with the given input and move the selection to the end of the
4773 // newly inserted text.
4774 let anchor = if in_adjacent_group || next_is_adjacent {
4775 // After edits the right bias would shift those anchor to the next visible fragment
4776 // but we want to resolve to the previous one
4777 snapshot.anchor_before(selection.end)
4778 } else {
4779 snapshot.anchor_after(selection.end)
4780 };
4781
4782 if !self.linked_edit_ranges.is_empty() {
4783 let start_anchor = snapshot.anchor_before(selection.start);
4784
4785 let is_word_char = text.chars().next().is_none_or(|char| {
4786 let classifier = snapshot
4787 .char_classifier_at(start_anchor.to_offset(&snapshot))
4788 .scope_context(Some(CharScopeContext::LinkedEdit));
4789 classifier.is_word(char)
4790 });
4791
4792 if is_word_char {
4793 if let Some(ranges) = self
4794 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4795 {
4796 for (buffer, edits) in ranges {
4797 linked_edits
4798 .entry(buffer.clone())
4799 .or_default()
4800 .extend(edits.into_iter().map(|range| (range, text.clone())));
4801 }
4802 }
4803 } else {
4804 clear_linked_edit_ranges = true;
4805 }
4806 }
4807
4808 new_selections.push((selection.map(|_| anchor), 0));
4809 edits.push((selection.start..selection.end, text.clone()));
4810
4811 has_adjacent_edits |= next_is_adjacent;
4812 in_adjacent_group = next_is_adjacent;
4813 }
4814
4815 if all_selections_read_only {
4816 return;
4817 }
4818
4819 drop(regions);
4820 drop(snapshot);
4821
4822 self.transact(window, cx, |this, window, cx| {
4823 if clear_linked_edit_ranges {
4824 this.linked_edit_ranges.clear();
4825 }
4826 let initial_buffer_versions =
4827 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4828
4829 this.buffer.update(cx, |buffer, cx| {
4830 if has_adjacent_edits {
4831 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4832 } else {
4833 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4834 }
4835 });
4836 for (buffer, edits) in linked_edits {
4837 buffer.update(cx, |buffer, cx| {
4838 let snapshot = buffer.snapshot();
4839 let edits = edits
4840 .into_iter()
4841 .map(|(range, text)| {
4842 use text::ToPoint as TP;
4843 let end_point = TP::to_point(&range.end, &snapshot);
4844 let start_point = TP::to_point(&range.start, &snapshot);
4845 (start_point..end_point, text)
4846 })
4847 .sorted_by_key(|(range, _)| range.start);
4848 buffer.edit(edits, None, cx);
4849 })
4850 }
4851 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4852 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4853 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4854 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4855 new_anchor_selections,
4856 &map,
4857 )
4858 .zip(new_selection_deltas)
4859 .map(|(selection, delta)| Selection {
4860 id: selection.id,
4861 start: selection.start + delta,
4862 end: selection.end + delta,
4863 reversed: selection.reversed,
4864 goal: SelectionGoal::None,
4865 })
4866 .collect::<Vec<_>>();
4867
4868 let mut i = 0;
4869 for (position, delta, selection_id, pair) in new_autoclose_regions {
4870 let position = position.to_offset(map.buffer_snapshot()) + delta;
4871 let start = map.buffer_snapshot().anchor_before(position);
4872 let end = map.buffer_snapshot().anchor_after(position);
4873 while let Some(existing_state) = this.autoclose_regions.get(i) {
4874 match existing_state
4875 .range
4876 .start
4877 .cmp(&start, map.buffer_snapshot())
4878 {
4879 Ordering::Less => i += 1,
4880 Ordering::Greater => break,
4881 Ordering::Equal => {
4882 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4883 Ordering::Less => i += 1,
4884 Ordering::Equal => break,
4885 Ordering::Greater => break,
4886 }
4887 }
4888 }
4889 }
4890 this.autoclose_regions.insert(
4891 i,
4892 AutocloseRegion {
4893 selection_id,
4894 range: start..end,
4895 pair,
4896 },
4897 );
4898 }
4899
4900 let had_active_edit_prediction = this.has_active_edit_prediction();
4901 this.change_selections(
4902 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4903 window,
4904 cx,
4905 |s| s.select(new_selections),
4906 );
4907
4908 if !bracket_inserted
4909 && let Some(on_type_format_task) =
4910 this.trigger_on_type_formatting(text.to_string(), window, cx)
4911 {
4912 on_type_format_task.detach_and_log_err(cx);
4913 }
4914
4915 let editor_settings = EditorSettings::get_global(cx);
4916 if bracket_inserted
4917 && (editor_settings.auto_signature_help
4918 || editor_settings.show_signature_help_after_edits)
4919 {
4920 this.show_signature_help(&ShowSignatureHelp, window, cx);
4921 }
4922
4923 let trigger_in_words =
4924 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4925 if this.hard_wrap.is_some() {
4926 let latest: Range<Point> = this.selections.newest(&map).range();
4927 if latest.is_empty()
4928 && this
4929 .buffer()
4930 .read(cx)
4931 .snapshot(cx)
4932 .line_len(MultiBufferRow(latest.start.row))
4933 == latest.start.column
4934 {
4935 this.rewrap_impl(
4936 RewrapOptions {
4937 override_language_settings: true,
4938 preserve_existing_whitespace: true,
4939 },
4940 cx,
4941 )
4942 }
4943 }
4944 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4945 refresh_linked_ranges(this, window, cx);
4946 this.refresh_edit_prediction(true, false, window, cx);
4947 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4948 });
4949 }
4950
4951 fn find_possible_emoji_shortcode_at_position(
4952 snapshot: &MultiBufferSnapshot,
4953 position: Point,
4954 ) -> Option<String> {
4955 let mut chars = Vec::new();
4956 let mut found_colon = false;
4957 for char in snapshot.reversed_chars_at(position).take(100) {
4958 // Found a possible emoji shortcode in the middle of the buffer
4959 if found_colon {
4960 if char.is_whitespace() {
4961 chars.reverse();
4962 return Some(chars.iter().collect());
4963 }
4964 // If the previous character is not a whitespace, we are in the middle of a word
4965 // and we only want to complete the shortcode if the word is made up of other emojis
4966 let mut containing_word = String::new();
4967 for ch in snapshot
4968 .reversed_chars_at(position)
4969 .skip(chars.len() + 1)
4970 .take(100)
4971 {
4972 if ch.is_whitespace() {
4973 break;
4974 }
4975 containing_word.push(ch);
4976 }
4977 let containing_word = containing_word.chars().rev().collect::<String>();
4978 if util::word_consists_of_emojis(containing_word.as_str()) {
4979 chars.reverse();
4980 return Some(chars.iter().collect());
4981 }
4982 }
4983
4984 if char.is_whitespace() || !char.is_ascii() {
4985 return None;
4986 }
4987 if char == ':' {
4988 found_colon = true;
4989 } else {
4990 chars.push(char);
4991 }
4992 }
4993 // Found a possible emoji shortcode at the beginning of the buffer
4994 chars.reverse();
4995 Some(chars.iter().collect())
4996 }
4997
4998 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5000 self.transact(window, cx, |this, window, cx| {
5001 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5002 let selections = this
5003 .selections
5004 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5005 let multi_buffer = this.buffer.read(cx);
5006 let buffer = multi_buffer.snapshot(cx);
5007 selections
5008 .iter()
5009 .map(|selection| {
5010 let start_point = selection.start.to_point(&buffer);
5011 let mut existing_indent =
5012 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5013 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5014 let start = selection.start;
5015 let end = selection.end;
5016 let selection_is_empty = start == end;
5017 let language_scope = buffer.language_scope_at(start);
5018 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5019 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5020 &buffer,
5021 start..end,
5022 language,
5023 )
5024 || NewlineConfig::insert_extra_newline_tree_sitter(
5025 &buffer,
5026 start..end,
5027 );
5028
5029 let mut newline_config = NewlineConfig::Newline {
5030 additional_indent: IndentSize::spaces(0),
5031 extra_line_additional_indent: if needs_extra_newline {
5032 Some(IndentSize::spaces(0))
5033 } else {
5034 None
5035 },
5036 prevent_auto_indent: false,
5037 };
5038
5039 let comment_delimiter = maybe!({
5040 if !selection_is_empty {
5041 return None;
5042 }
5043
5044 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5045 return None;
5046 }
5047
5048 return comment_delimiter_for_newline(
5049 &start_point,
5050 &buffer,
5051 language,
5052 );
5053 });
5054
5055 let doc_delimiter = maybe!({
5056 if !selection_is_empty {
5057 return None;
5058 }
5059
5060 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5061 return None;
5062 }
5063
5064 return documentation_delimiter_for_newline(
5065 &start_point,
5066 &buffer,
5067 language,
5068 &mut newline_config,
5069 );
5070 });
5071
5072 let list_delimiter = maybe!({
5073 if !selection_is_empty {
5074 return None;
5075 }
5076
5077 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5078 return None;
5079 }
5080
5081 return list_delimiter_for_newline(
5082 &start_point,
5083 &buffer,
5084 language,
5085 &mut newline_config,
5086 );
5087 });
5088
5089 (
5090 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5091 newline_config,
5092 )
5093 } else {
5094 (
5095 None,
5096 NewlineConfig::Newline {
5097 additional_indent: IndentSize::spaces(0),
5098 extra_line_additional_indent: None,
5099 prevent_auto_indent: false,
5100 },
5101 )
5102 };
5103
5104 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5105 NewlineConfig::ClearCurrentLine => {
5106 let row_start =
5107 buffer.point_to_offset(Point::new(start_point.row, 0));
5108 (row_start, String::new(), false)
5109 }
5110 NewlineConfig::UnindentCurrentLine { continuation } => {
5111 let row_start =
5112 buffer.point_to_offset(Point::new(start_point.row, 0));
5113 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5114 let tab_size_indent = IndentSize::spaces(tab_size.get());
5115 let reduced_indent =
5116 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5117 let mut new_text = String::new();
5118 new_text.extend(reduced_indent.chars());
5119 new_text.push_str(continuation);
5120 (row_start, new_text, true)
5121 }
5122 NewlineConfig::Newline {
5123 additional_indent,
5124 extra_line_additional_indent,
5125 prevent_auto_indent,
5126 } => {
5127 let capacity_for_delimiter =
5128 delimiter.as_deref().map(str::len).unwrap_or_default();
5129 let extra_line_len = extra_line_additional_indent
5130 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5131 .unwrap_or(0);
5132 let mut new_text = String::with_capacity(
5133 1 + capacity_for_delimiter
5134 + existing_indent.len as usize
5135 + additional_indent.len as usize
5136 + extra_line_len,
5137 );
5138 new_text.push('\n');
5139 new_text.extend(existing_indent.chars());
5140 new_text.extend(additional_indent.chars());
5141 if let Some(delimiter) = &delimiter {
5142 new_text.push_str(delimiter);
5143 }
5144 if let Some(extra_indent) = extra_line_additional_indent {
5145 new_text.push('\n');
5146 new_text.extend(existing_indent.chars());
5147 new_text.extend(extra_indent.chars());
5148 }
5149 (start, new_text, *prevent_auto_indent)
5150 }
5151 };
5152
5153 let anchor = buffer.anchor_after(end);
5154 let new_selection = selection.map(|_| anchor);
5155 (
5156 ((edit_start..end, new_text), prevent_auto_indent),
5157 (newline_config.has_extra_line(), new_selection),
5158 )
5159 })
5160 .unzip()
5161 };
5162
5163 let mut auto_indent_edits = Vec::new();
5164 let mut edits = Vec::new();
5165 for (edit, prevent_auto_indent) in edits_with_flags {
5166 if prevent_auto_indent {
5167 edits.push(edit);
5168 } else {
5169 auto_indent_edits.push(edit);
5170 }
5171 }
5172 if !edits.is_empty() {
5173 this.edit(edits, cx);
5174 }
5175 if !auto_indent_edits.is_empty() {
5176 this.edit_with_autoindent(auto_indent_edits, cx);
5177 }
5178
5179 let buffer = this.buffer.read(cx).snapshot(cx);
5180 let new_selections = selection_info
5181 .into_iter()
5182 .map(|(extra_newline_inserted, new_selection)| {
5183 let mut cursor = new_selection.end.to_point(&buffer);
5184 if extra_newline_inserted {
5185 cursor.row -= 1;
5186 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5187 }
5188 new_selection.map(|_| cursor)
5189 })
5190 .collect();
5191
5192 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5193 this.refresh_edit_prediction(true, false, window, cx);
5194 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5195 task.detach_and_log_err(cx);
5196 }
5197 });
5198 }
5199
5200 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5201 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5202
5203 let buffer = self.buffer.read(cx);
5204 let snapshot = buffer.snapshot(cx);
5205
5206 let mut edits = Vec::new();
5207 let mut rows = Vec::new();
5208
5209 for (rows_inserted, selection) in self
5210 .selections
5211 .all_adjusted(&self.display_snapshot(cx))
5212 .into_iter()
5213 .enumerate()
5214 {
5215 let cursor = selection.head();
5216 let row = cursor.row;
5217
5218 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5219
5220 let newline = "\n".to_string();
5221 edits.push((start_of_line..start_of_line, newline));
5222
5223 rows.push(row + rows_inserted as u32);
5224 }
5225
5226 self.transact(window, cx, |editor, window, cx| {
5227 editor.edit(edits, cx);
5228
5229 editor.change_selections(Default::default(), window, cx, |s| {
5230 let mut index = 0;
5231 s.move_cursors_with(|map, _, _| {
5232 let row = rows[index];
5233 index += 1;
5234
5235 let point = Point::new(row, 0);
5236 let boundary = map.next_line_boundary(point).1;
5237 let clipped = map.clip_point(boundary, Bias::Left);
5238
5239 (clipped, SelectionGoal::None)
5240 });
5241 });
5242
5243 let mut indent_edits = Vec::new();
5244 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5245 for row in rows {
5246 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5247 for (row, indent) in indents {
5248 if indent.len == 0 {
5249 continue;
5250 }
5251
5252 let text = match indent.kind {
5253 IndentKind::Space => " ".repeat(indent.len as usize),
5254 IndentKind::Tab => "\t".repeat(indent.len as usize),
5255 };
5256 let point = Point::new(row.0, 0);
5257 indent_edits.push((point..point, text));
5258 }
5259 }
5260 editor.edit(indent_edits, cx);
5261 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5262 format.detach_and_log_err(cx);
5263 }
5264 });
5265 }
5266
5267 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5268 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5269
5270 let buffer = self.buffer.read(cx);
5271 let snapshot = buffer.snapshot(cx);
5272
5273 let mut edits = Vec::new();
5274 let mut rows = Vec::new();
5275 let mut rows_inserted = 0;
5276
5277 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5278 let cursor = selection.head();
5279 let row = cursor.row;
5280
5281 let point = Point::new(row + 1, 0);
5282 let start_of_line = snapshot.clip_point(point, Bias::Left);
5283
5284 let newline = "\n".to_string();
5285 edits.push((start_of_line..start_of_line, newline));
5286
5287 rows_inserted += 1;
5288 rows.push(row + rows_inserted);
5289 }
5290
5291 self.transact(window, cx, |editor, window, cx| {
5292 editor.edit(edits, cx);
5293
5294 editor.change_selections(Default::default(), window, cx, |s| {
5295 let mut index = 0;
5296 s.move_cursors_with(|map, _, _| {
5297 let row = rows[index];
5298 index += 1;
5299
5300 let point = Point::new(row, 0);
5301 let boundary = map.next_line_boundary(point).1;
5302 let clipped = map.clip_point(boundary, Bias::Left);
5303
5304 (clipped, SelectionGoal::None)
5305 });
5306 });
5307
5308 let mut indent_edits = Vec::new();
5309 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5310 for row in rows {
5311 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5312 for (row, indent) in indents {
5313 if indent.len == 0 {
5314 continue;
5315 }
5316
5317 let text = match indent.kind {
5318 IndentKind::Space => " ".repeat(indent.len as usize),
5319 IndentKind::Tab => "\t".repeat(indent.len as usize),
5320 };
5321 let point = Point::new(row.0, 0);
5322 indent_edits.push((point..point, text));
5323 }
5324 }
5325 editor.edit(indent_edits, cx);
5326 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5327 format.detach_and_log_err(cx);
5328 }
5329 });
5330 }
5331
5332 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5333 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5334 original_indent_columns: Vec::new(),
5335 });
5336 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5337 }
5338
5339 fn insert_with_autoindent_mode(
5340 &mut self,
5341 text: &str,
5342 autoindent_mode: Option<AutoindentMode>,
5343 window: &mut Window,
5344 cx: &mut Context<Self>,
5345 ) {
5346 if self.read_only(cx) {
5347 return;
5348 }
5349
5350 let text: Arc<str> = text.into();
5351 self.transact(window, cx, |this, window, cx| {
5352 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5353 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5354 let anchors = {
5355 let snapshot = buffer.read(cx);
5356 old_selections
5357 .iter()
5358 .map(|s| {
5359 let anchor = snapshot.anchor_after(s.head());
5360 s.map(|_| anchor)
5361 })
5362 .collect::<Vec<_>>()
5363 };
5364 buffer.edit(
5365 old_selections
5366 .iter()
5367 .map(|s| (s.start..s.end, text.clone())),
5368 autoindent_mode,
5369 cx,
5370 );
5371 anchors
5372 });
5373
5374 this.change_selections(Default::default(), window, cx, |s| {
5375 s.select_anchors(selection_anchors);
5376 });
5377
5378 cx.notify();
5379 });
5380 }
5381
5382 fn trigger_completion_on_input(
5383 &mut self,
5384 text: &str,
5385 trigger_in_words: bool,
5386 window: &mut Window,
5387 cx: &mut Context<Self>,
5388 ) {
5389 let completions_source = self
5390 .context_menu
5391 .borrow()
5392 .as_ref()
5393 .and_then(|menu| match menu {
5394 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5395 CodeContextMenu::CodeActions(_) => None,
5396 });
5397
5398 match completions_source {
5399 Some(CompletionsMenuSource::Words { .. }) => {
5400 self.open_or_update_completions_menu(
5401 Some(CompletionsMenuSource::Words {
5402 ignore_threshold: false,
5403 }),
5404 None,
5405 trigger_in_words,
5406 window,
5407 cx,
5408 );
5409 }
5410 _ => self.open_or_update_completions_menu(
5411 None,
5412 Some(text.to_owned()).filter(|x| !x.is_empty()),
5413 true,
5414 window,
5415 cx,
5416 ),
5417 }
5418 }
5419
5420 /// If any empty selections is touching the start of its innermost containing autoclose
5421 /// region, expand it to select the brackets.
5422 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5423 let selections = self
5424 .selections
5425 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5426 let buffer = self.buffer.read(cx).read(cx);
5427 let new_selections = self
5428 .selections_with_autoclose_regions(selections, &buffer)
5429 .map(|(mut selection, region)| {
5430 if !selection.is_empty() {
5431 return selection;
5432 }
5433
5434 if let Some(region) = region {
5435 let mut range = region.range.to_offset(&buffer);
5436 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5437 range.start -= region.pair.start.len();
5438 if buffer.contains_str_at(range.start, ®ion.pair.start)
5439 && buffer.contains_str_at(range.end, ®ion.pair.end)
5440 {
5441 range.end += region.pair.end.len();
5442 selection.start = range.start;
5443 selection.end = range.end;
5444
5445 return selection;
5446 }
5447 }
5448 }
5449
5450 let always_treat_brackets_as_autoclosed = buffer
5451 .language_settings_at(selection.start, cx)
5452 .always_treat_brackets_as_autoclosed;
5453
5454 if !always_treat_brackets_as_autoclosed {
5455 return selection;
5456 }
5457
5458 if let Some(scope) = buffer.language_scope_at(selection.start) {
5459 for (pair, enabled) in scope.brackets() {
5460 if !enabled || !pair.close {
5461 continue;
5462 }
5463
5464 if buffer.contains_str_at(selection.start, &pair.end) {
5465 let pair_start_len = pair.start.len();
5466 if buffer.contains_str_at(
5467 selection.start.saturating_sub_usize(pair_start_len),
5468 &pair.start,
5469 ) {
5470 selection.start -= pair_start_len;
5471 selection.end += pair.end.len();
5472
5473 return selection;
5474 }
5475 }
5476 }
5477 }
5478
5479 selection
5480 })
5481 .collect();
5482
5483 drop(buffer);
5484 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5485 selections.select(new_selections)
5486 });
5487 }
5488
5489 /// Iterate the given selections, and for each one, find the smallest surrounding
5490 /// autoclose region. This uses the ordering of the selections and the autoclose
5491 /// regions to avoid repeated comparisons.
5492 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5493 &'a self,
5494 selections: impl IntoIterator<Item = Selection<D>>,
5495 buffer: &'a MultiBufferSnapshot,
5496 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5497 let mut i = 0;
5498 let mut regions = self.autoclose_regions.as_slice();
5499 selections.into_iter().map(move |selection| {
5500 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5501
5502 let mut enclosing = None;
5503 while let Some(pair_state) = regions.get(i) {
5504 if pair_state.range.end.to_offset(buffer) < range.start {
5505 regions = ®ions[i + 1..];
5506 i = 0;
5507 } else if pair_state.range.start.to_offset(buffer) > range.end {
5508 break;
5509 } else {
5510 if pair_state.selection_id == selection.id {
5511 enclosing = Some(pair_state);
5512 }
5513 i += 1;
5514 }
5515 }
5516
5517 (selection, enclosing)
5518 })
5519 }
5520
5521 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5522 fn invalidate_autoclose_regions(
5523 &mut self,
5524 mut selections: &[Selection<Anchor>],
5525 buffer: &MultiBufferSnapshot,
5526 ) {
5527 self.autoclose_regions.retain(|state| {
5528 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5529 return false;
5530 }
5531
5532 let mut i = 0;
5533 while let Some(selection) = selections.get(i) {
5534 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5535 selections = &selections[1..];
5536 continue;
5537 }
5538 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5539 break;
5540 }
5541 if selection.id == state.selection_id {
5542 return true;
5543 } else {
5544 i += 1;
5545 }
5546 }
5547 false
5548 });
5549 }
5550
5551 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5552 let offset = position.to_offset(buffer);
5553 let (word_range, kind) =
5554 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5555 if offset > word_range.start && kind == Some(CharKind::Word) {
5556 Some(
5557 buffer
5558 .text_for_range(word_range.start..offset)
5559 .collect::<String>(),
5560 )
5561 } else {
5562 None
5563 }
5564 }
5565
5566 pub fn visible_excerpts(
5567 &self,
5568 lsp_related_only: bool,
5569 cx: &mut Context<Editor>,
5570 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5571 let project = self.project().cloned();
5572 let multi_buffer = self.buffer().read(cx);
5573 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5574 let multi_buffer_visible_start = self
5575 .scroll_manager
5576 .anchor()
5577 .anchor
5578 .to_point(&multi_buffer_snapshot);
5579 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5580 multi_buffer_visible_start
5581 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5582 Bias::Left,
5583 );
5584 multi_buffer_snapshot
5585 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5586 .into_iter()
5587 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5588 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5589 if !lsp_related_only {
5590 return Some((
5591 excerpt_id,
5592 (
5593 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5594 buffer.version().clone(),
5595 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5596 ),
5597 ));
5598 }
5599
5600 let project = project.as_ref()?.read(cx);
5601 let buffer_file = project::File::from_dyn(buffer.file())?;
5602 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5603 let worktree_entry = buffer_worktree
5604 .read(cx)
5605 .entry_for_id(buffer_file.project_entry_id()?)?;
5606 if worktree_entry.is_ignored {
5607 None
5608 } else {
5609 Some((
5610 excerpt_id,
5611 (
5612 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5613 buffer.version().clone(),
5614 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5615 ),
5616 ))
5617 }
5618 })
5619 .collect()
5620 }
5621
5622 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5623 TextLayoutDetails {
5624 text_system: window.text_system().clone(),
5625 editor_style: self.style.clone().unwrap(),
5626 rem_size: window.rem_size(),
5627 scroll_anchor: self.scroll_manager.anchor(),
5628 visible_rows: self.visible_line_count(),
5629 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5630 }
5631 }
5632
5633 fn trigger_on_type_formatting(
5634 &self,
5635 input: String,
5636 window: &mut Window,
5637 cx: &mut Context<Self>,
5638 ) -> Option<Task<Result<()>>> {
5639 if input.chars().count() != 1 {
5640 return None;
5641 }
5642
5643 let project = self.project()?;
5644 let position = self.selections.newest_anchor().head();
5645 let (buffer, buffer_position) = self
5646 .buffer
5647 .read(cx)
5648 .text_anchor_for_position(position, cx)?;
5649
5650 let settings = language_settings::language_settings(
5651 buffer
5652 .read(cx)
5653 .language_at(buffer_position)
5654 .map(|l| l.name()),
5655 buffer.read(cx).file(),
5656 cx,
5657 );
5658 if !settings.use_on_type_format {
5659 return None;
5660 }
5661
5662 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5663 // hence we do LSP request & edit on host side only — add formats to host's history.
5664 let push_to_lsp_host_history = true;
5665 // If this is not the host, append its history with new edits.
5666 let push_to_client_history = project.read(cx).is_via_collab();
5667
5668 let on_type_formatting = project.update(cx, |project, cx| {
5669 project.on_type_format(
5670 buffer.clone(),
5671 buffer_position,
5672 input,
5673 push_to_lsp_host_history,
5674 cx,
5675 )
5676 });
5677 Some(cx.spawn_in(window, async move |editor, cx| {
5678 if let Some(transaction) = on_type_formatting.await? {
5679 if push_to_client_history {
5680 buffer.update(cx, |buffer, _| {
5681 buffer.push_transaction(transaction, Instant::now());
5682 buffer.finalize_last_transaction();
5683 });
5684 }
5685 editor.update(cx, |editor, cx| {
5686 editor.refresh_document_highlights(cx);
5687 })?;
5688 }
5689 Ok(())
5690 }))
5691 }
5692
5693 pub fn show_word_completions(
5694 &mut self,
5695 _: &ShowWordCompletions,
5696 window: &mut Window,
5697 cx: &mut Context<Self>,
5698 ) {
5699 self.open_or_update_completions_menu(
5700 Some(CompletionsMenuSource::Words {
5701 ignore_threshold: true,
5702 }),
5703 None,
5704 false,
5705 window,
5706 cx,
5707 );
5708 }
5709
5710 pub fn show_completions(
5711 &mut self,
5712 _: &ShowCompletions,
5713 window: &mut Window,
5714 cx: &mut Context<Self>,
5715 ) {
5716 self.open_or_update_completions_menu(None, None, false, window, cx);
5717 }
5718
5719 fn open_or_update_completions_menu(
5720 &mut self,
5721 requested_source: Option<CompletionsMenuSource>,
5722 trigger: Option<String>,
5723 trigger_in_words: bool,
5724 window: &mut Window,
5725 cx: &mut Context<Self>,
5726 ) {
5727 if self.pending_rename.is_some() {
5728 return;
5729 }
5730
5731 let completions_source = self
5732 .context_menu
5733 .borrow()
5734 .as_ref()
5735 .and_then(|menu| match menu {
5736 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5737 CodeContextMenu::CodeActions(_) => None,
5738 });
5739
5740 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5741
5742 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5743 // inserted and selected. To handle that case, the start of the selection is used so that
5744 // the menu starts with all choices.
5745 let position = self
5746 .selections
5747 .newest_anchor()
5748 .start
5749 .bias_right(&multibuffer_snapshot);
5750 if position.diff_base_anchor.is_some() {
5751 return;
5752 }
5753 let buffer_position = multibuffer_snapshot.anchor_before(position);
5754 let Some(buffer) = buffer_position
5755 .text_anchor
5756 .buffer_id
5757 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5758 else {
5759 return;
5760 };
5761 let buffer_snapshot = buffer.read(cx).snapshot();
5762
5763 let menu_is_open = matches!(
5764 self.context_menu.borrow().as_ref(),
5765 Some(CodeContextMenu::Completions(_))
5766 );
5767
5768 let language = buffer_snapshot
5769 .language_at(buffer_position.text_anchor)
5770 .map(|language| language.name());
5771
5772 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5773 let completion_settings = language_settings.completions.clone();
5774
5775 let show_completions_on_input = self
5776 .show_completions_on_input_override
5777 .unwrap_or(language_settings.show_completions_on_input);
5778 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5779 return;
5780 }
5781
5782 let query: Option<Arc<String>> =
5783 Self::completion_query(&multibuffer_snapshot, buffer_position)
5784 .map(|query| query.into());
5785
5786 drop(multibuffer_snapshot);
5787
5788 // Hide the current completions menu when query is empty. Without this, cached
5789 // completions from before the trigger char may be reused (#32774).
5790 if query.is_none() && menu_is_open {
5791 self.hide_context_menu(window, cx);
5792 }
5793
5794 let mut ignore_word_threshold = false;
5795 let provider = match requested_source {
5796 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5797 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5798 ignore_word_threshold = ignore_threshold;
5799 None
5800 }
5801 Some(CompletionsMenuSource::SnippetChoices)
5802 | Some(CompletionsMenuSource::SnippetsOnly) => {
5803 log::error!("bug: SnippetChoices requested_source is not handled");
5804 None
5805 }
5806 };
5807
5808 let sort_completions = provider
5809 .as_ref()
5810 .is_some_and(|provider| provider.sort_completions());
5811
5812 let filter_completions = provider
5813 .as_ref()
5814 .is_none_or(|provider| provider.filter_completions());
5815
5816 let was_snippets_only = matches!(
5817 completions_source,
5818 Some(CompletionsMenuSource::SnippetsOnly)
5819 );
5820
5821 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5822 if filter_completions {
5823 menu.filter(
5824 query.clone().unwrap_or_default(),
5825 buffer_position.text_anchor,
5826 &buffer,
5827 provider.clone(),
5828 window,
5829 cx,
5830 );
5831 }
5832 // When `is_incomplete` is false, no need to re-query completions when the current query
5833 // is a suffix of the initial query.
5834 let was_complete = !menu.is_incomplete;
5835 if was_complete && !was_snippets_only {
5836 // If the new query is a suffix of the old query (typing more characters) and
5837 // the previous result was complete, the existing completions can be filtered.
5838 //
5839 // Note that snippet completions are always complete.
5840 let query_matches = match (&menu.initial_query, &query) {
5841 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5842 (None, _) => true,
5843 _ => false,
5844 };
5845 if query_matches {
5846 let position_matches = if menu.initial_position == position {
5847 true
5848 } else {
5849 let snapshot = self.buffer.read(cx).read(cx);
5850 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5851 };
5852 if position_matches {
5853 return;
5854 }
5855 }
5856 }
5857 };
5858
5859 let Anchor {
5860 excerpt_id: buffer_excerpt_id,
5861 text_anchor: buffer_position,
5862 ..
5863 } = buffer_position;
5864
5865 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5866 buffer_snapshot.surrounding_word(buffer_position, None)
5867 {
5868 let word_to_exclude = buffer_snapshot
5869 .text_for_range(word_range.clone())
5870 .collect::<String>();
5871 (
5872 buffer_snapshot.anchor_before(word_range.start)
5873 ..buffer_snapshot.anchor_after(buffer_position),
5874 Some(word_to_exclude),
5875 )
5876 } else {
5877 (buffer_position..buffer_position, None)
5878 };
5879
5880 let show_completion_documentation = buffer_snapshot
5881 .settings_at(buffer_position, cx)
5882 .show_completion_documentation;
5883
5884 // The document can be large, so stay in reasonable bounds when searching for words,
5885 // otherwise completion pop-up might be slow to appear.
5886 const WORD_LOOKUP_ROWS: u32 = 5_000;
5887 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5888 let min_word_search = buffer_snapshot.clip_point(
5889 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5890 Bias::Left,
5891 );
5892 let max_word_search = buffer_snapshot.clip_point(
5893 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5894 Bias::Right,
5895 );
5896 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5897 ..buffer_snapshot.point_to_offset(max_word_search);
5898
5899 let skip_digits = query
5900 .as_ref()
5901 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5902
5903 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5904 trigger.as_ref().is_none_or(|trigger| {
5905 provider.is_completion_trigger(
5906 &buffer,
5907 position.text_anchor,
5908 trigger,
5909 trigger_in_words,
5910 cx,
5911 )
5912 })
5913 });
5914
5915 let provider_responses = if let Some(provider) = &provider
5916 && load_provider_completions
5917 {
5918 let trigger_character =
5919 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5920 let completion_context = CompletionContext {
5921 trigger_kind: match &trigger_character {
5922 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5923 None => CompletionTriggerKind::INVOKED,
5924 },
5925 trigger_character,
5926 };
5927
5928 provider.completions(
5929 buffer_excerpt_id,
5930 &buffer,
5931 buffer_position,
5932 completion_context,
5933 window,
5934 cx,
5935 )
5936 } else {
5937 Task::ready(Ok(Vec::new()))
5938 };
5939
5940 let load_word_completions = if !self.word_completions_enabled {
5941 false
5942 } else if requested_source
5943 == Some(CompletionsMenuSource::Words {
5944 ignore_threshold: true,
5945 })
5946 {
5947 true
5948 } else {
5949 load_provider_completions
5950 && completion_settings.words != WordsCompletionMode::Disabled
5951 && (ignore_word_threshold || {
5952 let words_min_length = completion_settings.words_min_length;
5953 // check whether word has at least `words_min_length` characters
5954 let query_chars = query.iter().flat_map(|q| q.chars());
5955 query_chars.take(words_min_length).count() == words_min_length
5956 })
5957 };
5958
5959 let mut words = if load_word_completions {
5960 cx.background_spawn({
5961 let buffer_snapshot = buffer_snapshot.clone();
5962 async move {
5963 buffer_snapshot.words_in_range(WordsQuery {
5964 fuzzy_contents: None,
5965 range: word_search_range,
5966 skip_digits,
5967 })
5968 }
5969 })
5970 } else {
5971 Task::ready(BTreeMap::default())
5972 };
5973
5974 let snippets = if let Some(provider) = &provider
5975 && provider.show_snippets()
5976 && let Some(project) = self.project()
5977 {
5978 let char_classifier = buffer_snapshot
5979 .char_classifier_at(buffer_position)
5980 .scope_context(Some(CharScopeContext::Completion));
5981 project.update(cx, |project, cx| {
5982 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5983 })
5984 } else {
5985 Task::ready(Ok(CompletionResponse {
5986 completions: Vec::new(),
5987 display_options: Default::default(),
5988 is_incomplete: false,
5989 }))
5990 };
5991
5992 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5993
5994 let id = post_inc(&mut self.next_completion_id);
5995 let task = cx.spawn_in(window, async move |editor, cx| {
5996 let Ok(()) = editor.update(cx, |this, _| {
5997 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5998 }) else {
5999 return;
6000 };
6001
6002 // TODO: Ideally completions from different sources would be selectively re-queried, so
6003 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6004 let mut completions = Vec::new();
6005 let mut is_incomplete = false;
6006 let mut display_options: Option<CompletionDisplayOptions> = None;
6007 if let Some(provider_responses) = provider_responses.await.log_err()
6008 && !provider_responses.is_empty()
6009 {
6010 for response in provider_responses {
6011 completions.extend(response.completions);
6012 is_incomplete = is_incomplete || response.is_incomplete;
6013 match display_options.as_mut() {
6014 None => {
6015 display_options = Some(response.display_options);
6016 }
6017 Some(options) => options.merge(&response.display_options),
6018 }
6019 }
6020 if completion_settings.words == WordsCompletionMode::Fallback {
6021 words = Task::ready(BTreeMap::default());
6022 }
6023 }
6024 let display_options = display_options.unwrap_or_default();
6025
6026 let mut words = words.await;
6027 if let Some(word_to_exclude) = &word_to_exclude {
6028 words.remove(word_to_exclude);
6029 }
6030 for lsp_completion in &completions {
6031 words.remove(&lsp_completion.new_text);
6032 }
6033 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6034 replace_range: word_replace_range.clone(),
6035 new_text: word.clone(),
6036 label: CodeLabel::plain(word, None),
6037 match_start: None,
6038 snippet_deduplication_key: None,
6039 icon_path: None,
6040 documentation: None,
6041 source: CompletionSource::BufferWord {
6042 word_range,
6043 resolved: false,
6044 },
6045 insert_text_mode: Some(InsertTextMode::AS_IS),
6046 confirm: None,
6047 }));
6048
6049 completions.extend(
6050 snippets
6051 .await
6052 .into_iter()
6053 .flat_map(|response| response.completions),
6054 );
6055
6056 let menu = if completions.is_empty() {
6057 None
6058 } else {
6059 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6060 let languages = editor
6061 .workspace
6062 .as_ref()
6063 .and_then(|(workspace, _)| workspace.upgrade())
6064 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6065 let menu = CompletionsMenu::new(
6066 id,
6067 requested_source.unwrap_or(if load_provider_completions {
6068 CompletionsMenuSource::Normal
6069 } else {
6070 CompletionsMenuSource::SnippetsOnly
6071 }),
6072 sort_completions,
6073 show_completion_documentation,
6074 position,
6075 query.clone(),
6076 is_incomplete,
6077 buffer.clone(),
6078 completions.into(),
6079 editor
6080 .context_menu()
6081 .borrow_mut()
6082 .as_ref()
6083 .map(|menu| menu.primary_scroll_handle()),
6084 display_options,
6085 snippet_sort_order,
6086 languages,
6087 language,
6088 cx,
6089 );
6090
6091 let query = if filter_completions { query } else { None };
6092 let matches_task = menu.do_async_filtering(
6093 query.unwrap_or_default(),
6094 buffer_position,
6095 &buffer,
6096 cx,
6097 );
6098 (menu, matches_task)
6099 }) else {
6100 return;
6101 };
6102
6103 let matches = matches_task.await;
6104
6105 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6106 // Newer menu already set, so exit.
6107 if let Some(CodeContextMenu::Completions(prev_menu)) =
6108 editor.context_menu.borrow().as_ref()
6109 && prev_menu.id > id
6110 {
6111 return;
6112 };
6113
6114 // Only valid to take prev_menu because either the new menu is immediately set
6115 // below, or the menu is hidden.
6116 if let Some(CodeContextMenu::Completions(prev_menu)) =
6117 editor.context_menu.borrow_mut().take()
6118 {
6119 let position_matches =
6120 if prev_menu.initial_position == menu.initial_position {
6121 true
6122 } else {
6123 let snapshot = editor.buffer.read(cx).read(cx);
6124 prev_menu.initial_position.to_offset(&snapshot)
6125 == menu.initial_position.to_offset(&snapshot)
6126 };
6127 if position_matches {
6128 // Preserve markdown cache before `set_filter_results` because it will
6129 // try to populate the documentation cache.
6130 menu.preserve_markdown_cache(prev_menu);
6131 }
6132 };
6133
6134 menu.set_filter_results(matches, provider, window, cx);
6135 }) else {
6136 return;
6137 };
6138
6139 menu.visible().then_some(menu)
6140 };
6141
6142 editor
6143 .update_in(cx, |editor, window, cx| {
6144 if editor.focus_handle.is_focused(window)
6145 && let Some(menu) = menu
6146 {
6147 *editor.context_menu.borrow_mut() =
6148 Some(CodeContextMenu::Completions(menu));
6149
6150 crate::hover_popover::hide_hover(editor, cx);
6151 if editor.show_edit_predictions_in_menu() {
6152 editor.update_visible_edit_prediction(window, cx);
6153 } else {
6154 editor.discard_edit_prediction(false, cx);
6155 }
6156
6157 cx.notify();
6158 return;
6159 }
6160
6161 if editor.completion_tasks.len() <= 1 {
6162 // If there are no more completion tasks and the last menu was empty, we should hide it.
6163 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6164 // If it was already hidden and we don't show edit predictions in the menu,
6165 // we should also show the edit prediction when available.
6166 if was_hidden && editor.show_edit_predictions_in_menu() {
6167 editor.update_visible_edit_prediction(window, cx);
6168 }
6169 }
6170 })
6171 .ok();
6172 });
6173
6174 self.completion_tasks.push((id, task));
6175 }
6176
6177 #[cfg(feature = "test-support")]
6178 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6179 let menu = self.context_menu.borrow();
6180 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6181 let completions = menu.completions.borrow();
6182 Some(completions.to_vec())
6183 } else {
6184 None
6185 }
6186 }
6187
6188 pub fn with_completions_menu_matching_id<R>(
6189 &self,
6190 id: CompletionId,
6191 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6192 ) -> R {
6193 let mut context_menu = self.context_menu.borrow_mut();
6194 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6195 return f(None);
6196 };
6197 if completions_menu.id != id {
6198 return f(None);
6199 }
6200 f(Some(completions_menu))
6201 }
6202
6203 pub fn confirm_completion(
6204 &mut self,
6205 action: &ConfirmCompletion,
6206 window: &mut Window,
6207 cx: &mut Context<Self>,
6208 ) -> Option<Task<Result<()>>> {
6209 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6210 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6211 }
6212
6213 pub fn confirm_completion_insert(
6214 &mut self,
6215 _: &ConfirmCompletionInsert,
6216 window: &mut Window,
6217 cx: &mut Context<Self>,
6218 ) -> Option<Task<Result<()>>> {
6219 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6220 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6221 }
6222
6223 pub fn confirm_completion_replace(
6224 &mut self,
6225 _: &ConfirmCompletionReplace,
6226 window: &mut Window,
6227 cx: &mut Context<Self>,
6228 ) -> Option<Task<Result<()>>> {
6229 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6230 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6231 }
6232
6233 pub fn compose_completion(
6234 &mut self,
6235 action: &ComposeCompletion,
6236 window: &mut Window,
6237 cx: &mut Context<Self>,
6238 ) -> Option<Task<Result<()>>> {
6239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6240 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6241 }
6242
6243 fn do_completion(
6244 &mut self,
6245 item_ix: Option<usize>,
6246 intent: CompletionIntent,
6247 window: &mut Window,
6248 cx: &mut Context<Editor>,
6249 ) -> Option<Task<Result<()>>> {
6250 use language::ToOffset as _;
6251
6252 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6253 else {
6254 return None;
6255 };
6256
6257 let candidate_id = {
6258 let entries = completions_menu.entries.borrow();
6259 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6260 if self.show_edit_predictions_in_menu() {
6261 self.discard_edit_prediction(true, cx);
6262 }
6263 mat.candidate_id
6264 };
6265
6266 let completion = completions_menu
6267 .completions
6268 .borrow()
6269 .get(candidate_id)?
6270 .clone();
6271 cx.stop_propagation();
6272
6273 let buffer_handle = completions_menu.buffer.clone();
6274
6275 let CompletionEdit {
6276 new_text,
6277 snippet,
6278 replace_range,
6279 } = process_completion_for_edit(
6280 &completion,
6281 intent,
6282 &buffer_handle,
6283 &completions_menu.initial_position.text_anchor,
6284 cx,
6285 );
6286
6287 let buffer = buffer_handle.read(cx);
6288 let snapshot = self.buffer.read(cx).snapshot(cx);
6289 let newest_anchor = self.selections.newest_anchor();
6290 let replace_range_multibuffer = {
6291 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6292 excerpt.map_range_from_buffer(replace_range.clone())
6293 };
6294 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6295 return None;
6296 }
6297
6298 let old_text = buffer
6299 .text_for_range(replace_range.clone())
6300 .collect::<String>();
6301 let lookbehind = newest_anchor
6302 .start
6303 .text_anchor
6304 .to_offset(buffer)
6305 .saturating_sub(replace_range.start.0);
6306 let lookahead = replace_range
6307 .end
6308 .0
6309 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6310 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6311 let suffix = &old_text[lookbehind.min(old_text.len())..];
6312
6313 let selections = self
6314 .selections
6315 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6316 let mut ranges = Vec::new();
6317 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6318
6319 for selection in &selections {
6320 let range = if selection.id == newest_anchor.id {
6321 replace_range_multibuffer.clone()
6322 } else {
6323 let mut range = selection.range();
6324
6325 // if prefix is present, don't duplicate it
6326 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6327 range.start = range.start.saturating_sub_usize(lookbehind);
6328
6329 // if suffix is also present, mimic the newest cursor and replace it
6330 if selection.id != newest_anchor.id
6331 && snapshot.contains_str_at(range.end, suffix)
6332 {
6333 range.end += lookahead;
6334 }
6335 }
6336 range
6337 };
6338
6339 ranges.push(range.clone());
6340
6341 if !self.linked_edit_ranges.is_empty() {
6342 let start_anchor = snapshot.anchor_before(range.start);
6343 let end_anchor = snapshot.anchor_after(range.end);
6344 if let Some(ranges) = self
6345 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6346 {
6347 for (buffer, edits) in ranges {
6348 linked_edits
6349 .entry(buffer.clone())
6350 .or_default()
6351 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6352 }
6353 }
6354 }
6355 }
6356
6357 let common_prefix_len = old_text
6358 .chars()
6359 .zip(new_text.chars())
6360 .take_while(|(a, b)| a == b)
6361 .map(|(a, _)| a.len_utf8())
6362 .sum::<usize>();
6363
6364 cx.emit(EditorEvent::InputHandled {
6365 utf16_range_to_replace: None,
6366 text: new_text[common_prefix_len..].into(),
6367 });
6368
6369 self.transact(window, cx, |editor, window, cx| {
6370 if let Some(mut snippet) = snippet {
6371 snippet.text = new_text.to_string();
6372 editor
6373 .insert_snippet(&ranges, snippet, window, cx)
6374 .log_err();
6375 } else {
6376 editor.buffer.update(cx, |multi_buffer, cx| {
6377 let auto_indent = match completion.insert_text_mode {
6378 Some(InsertTextMode::AS_IS) => None,
6379 _ => editor.autoindent_mode.clone(),
6380 };
6381 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6382 multi_buffer.edit(edits, auto_indent, cx);
6383 });
6384 }
6385 for (buffer, edits) in linked_edits {
6386 buffer.update(cx, |buffer, cx| {
6387 let snapshot = buffer.snapshot();
6388 let edits = edits
6389 .into_iter()
6390 .map(|(range, text)| {
6391 use text::ToPoint as TP;
6392 let end_point = TP::to_point(&range.end, &snapshot);
6393 let start_point = TP::to_point(&range.start, &snapshot);
6394 (start_point..end_point, text)
6395 })
6396 .sorted_by_key(|(range, _)| range.start);
6397 buffer.edit(edits, None, cx);
6398 })
6399 }
6400
6401 editor.refresh_edit_prediction(true, false, window, cx);
6402 });
6403 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6404
6405 let show_new_completions_on_confirm = completion
6406 .confirm
6407 .as_ref()
6408 .is_some_and(|confirm| confirm(intent, window, cx));
6409 if show_new_completions_on_confirm {
6410 self.open_or_update_completions_menu(None, None, false, window, cx);
6411 }
6412
6413 let provider = self.completion_provider.as_ref()?;
6414
6415 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6416 let command = lsp_store.as_ref().and_then(|lsp_store| {
6417 let CompletionSource::Lsp {
6418 lsp_completion,
6419 server_id,
6420 ..
6421 } = &completion.source
6422 else {
6423 return None;
6424 };
6425 let lsp_command = lsp_completion.command.as_ref()?;
6426 let available_commands = lsp_store
6427 .read(cx)
6428 .lsp_server_capabilities
6429 .get(server_id)
6430 .and_then(|server_capabilities| {
6431 server_capabilities
6432 .execute_command_provider
6433 .as_ref()
6434 .map(|options| options.commands.as_slice())
6435 })?;
6436 if available_commands.contains(&lsp_command.command) {
6437 Some(CodeAction {
6438 server_id: *server_id,
6439 range: language::Anchor::MIN..language::Anchor::MIN,
6440 lsp_action: LspAction::Command(lsp_command.clone()),
6441 resolved: false,
6442 })
6443 } else {
6444 None
6445 }
6446 });
6447
6448 drop(completion);
6449 let apply_edits = provider.apply_additional_edits_for_completion(
6450 buffer_handle.clone(),
6451 completions_menu.completions.clone(),
6452 candidate_id,
6453 true,
6454 cx,
6455 );
6456
6457 let editor_settings = EditorSettings::get_global(cx);
6458 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6459 // After the code completion is finished, users often want to know what signatures are needed.
6460 // so we should automatically call signature_help
6461 self.show_signature_help(&ShowSignatureHelp, window, cx);
6462 }
6463
6464 Some(cx.spawn_in(window, async move |editor, cx| {
6465 apply_edits.await?;
6466
6467 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6468 let title = command.lsp_action.title().to_owned();
6469 let project_transaction = lsp_store
6470 .update(cx, |lsp_store, cx| {
6471 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6472 })
6473 .await
6474 .context("applying post-completion command")?;
6475 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6476 Self::open_project_transaction(
6477 &editor,
6478 workspace.downgrade(),
6479 project_transaction,
6480 title,
6481 cx,
6482 )
6483 .await?;
6484 }
6485 }
6486
6487 Ok(())
6488 }))
6489 }
6490
6491 pub fn toggle_code_actions(
6492 &mut self,
6493 action: &ToggleCodeActions,
6494 window: &mut Window,
6495 cx: &mut Context<Self>,
6496 ) {
6497 let quick_launch = action.quick_launch;
6498 let mut context_menu = self.context_menu.borrow_mut();
6499 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6500 if code_actions.deployed_from == action.deployed_from {
6501 // Toggle if we're selecting the same one
6502 *context_menu = None;
6503 cx.notify();
6504 return;
6505 } else {
6506 // Otherwise, clear it and start a new one
6507 *context_menu = None;
6508 cx.notify();
6509 }
6510 }
6511 drop(context_menu);
6512 let snapshot = self.snapshot(window, cx);
6513 let deployed_from = action.deployed_from.clone();
6514 let action = action.clone();
6515 self.completion_tasks.clear();
6516 self.discard_edit_prediction(false, cx);
6517
6518 let multibuffer_point = match &action.deployed_from {
6519 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6520 DisplayPoint::new(*row, 0).to_point(&snapshot)
6521 }
6522 _ => self
6523 .selections
6524 .newest::<Point>(&snapshot.display_snapshot)
6525 .head(),
6526 };
6527 let Some((buffer, buffer_row)) = snapshot
6528 .buffer_snapshot()
6529 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6530 .and_then(|(buffer_snapshot, range)| {
6531 self.buffer()
6532 .read(cx)
6533 .buffer(buffer_snapshot.remote_id())
6534 .map(|buffer| (buffer, range.start.row))
6535 })
6536 else {
6537 return;
6538 };
6539 let buffer_id = buffer.read(cx).remote_id();
6540 let tasks = self
6541 .tasks
6542 .get(&(buffer_id, buffer_row))
6543 .map(|t| Arc::new(t.to_owned()));
6544
6545 if !self.focus_handle.is_focused(window) {
6546 return;
6547 }
6548 let project = self.project.clone();
6549
6550 let code_actions_task = match deployed_from {
6551 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6552 _ => self.code_actions(buffer_row, window, cx),
6553 };
6554
6555 let runnable_task = match deployed_from {
6556 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6557 _ => {
6558 let mut task_context_task = Task::ready(None);
6559 if let Some(tasks) = &tasks
6560 && let Some(project) = project
6561 {
6562 task_context_task =
6563 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6564 }
6565
6566 cx.spawn_in(window, {
6567 let buffer = buffer.clone();
6568 async move |editor, cx| {
6569 let task_context = task_context_task.await;
6570
6571 let resolved_tasks =
6572 tasks
6573 .zip(task_context.clone())
6574 .map(|(tasks, task_context)| ResolvedTasks {
6575 templates: tasks.resolve(&task_context).collect(),
6576 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6577 multibuffer_point.row,
6578 tasks.column,
6579 )),
6580 });
6581 let debug_scenarios = editor
6582 .update(cx, |editor, cx| {
6583 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6584 })?
6585 .await;
6586 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6587 }
6588 })
6589 }
6590 };
6591
6592 cx.spawn_in(window, async move |editor, cx| {
6593 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6594 let code_actions = code_actions_task.await;
6595 let spawn_straight_away = quick_launch
6596 && resolved_tasks
6597 .as_ref()
6598 .is_some_and(|tasks| tasks.templates.len() == 1)
6599 && code_actions
6600 .as_ref()
6601 .is_none_or(|actions| actions.is_empty())
6602 && debug_scenarios.is_empty();
6603
6604 editor.update_in(cx, |editor, window, cx| {
6605 crate::hover_popover::hide_hover(editor, cx);
6606 let actions = CodeActionContents::new(
6607 resolved_tasks,
6608 code_actions,
6609 debug_scenarios,
6610 task_context.unwrap_or_default(),
6611 );
6612
6613 // Don't show the menu if there are no actions available
6614 if actions.is_empty() {
6615 cx.notify();
6616 return Task::ready(Ok(()));
6617 }
6618
6619 *editor.context_menu.borrow_mut() =
6620 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6621 buffer,
6622 actions,
6623 selected_item: Default::default(),
6624 scroll_handle: UniformListScrollHandle::default(),
6625 deployed_from,
6626 }));
6627 cx.notify();
6628 if spawn_straight_away
6629 && let Some(task) = editor.confirm_code_action(
6630 &ConfirmCodeAction { item_ix: Some(0) },
6631 window,
6632 cx,
6633 )
6634 {
6635 return task;
6636 }
6637
6638 Task::ready(Ok(()))
6639 })
6640 })
6641 .detach_and_log_err(cx);
6642 }
6643
6644 fn debug_scenarios(
6645 &mut self,
6646 resolved_tasks: &Option<ResolvedTasks>,
6647 buffer: &Entity<Buffer>,
6648 cx: &mut App,
6649 ) -> Task<Vec<task::DebugScenario>> {
6650 maybe!({
6651 let project = self.project()?;
6652 let dap_store = project.read(cx).dap_store();
6653 let mut scenarios = vec![];
6654 let resolved_tasks = resolved_tasks.as_ref()?;
6655 let buffer = buffer.read(cx);
6656 let language = buffer.language()?;
6657 let file = buffer.file();
6658 let debug_adapter = language_settings(language.name().into(), file, cx)
6659 .debuggers
6660 .first()
6661 .map(SharedString::from)
6662 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6663
6664 dap_store.update(cx, |dap_store, cx| {
6665 for (_, task) in &resolved_tasks.templates {
6666 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6667 task.original_task().clone(),
6668 debug_adapter.clone().into(),
6669 task.display_label().to_owned().into(),
6670 cx,
6671 );
6672 scenarios.push(maybe_scenario);
6673 }
6674 });
6675 Some(cx.background_spawn(async move {
6676 futures::future::join_all(scenarios)
6677 .await
6678 .into_iter()
6679 .flatten()
6680 .collect::<Vec<_>>()
6681 }))
6682 })
6683 .unwrap_or_else(|| Task::ready(vec![]))
6684 }
6685
6686 fn code_actions(
6687 &mut self,
6688 buffer_row: u32,
6689 window: &mut Window,
6690 cx: &mut Context<Self>,
6691 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6692 let mut task = self.code_actions_task.take();
6693 cx.spawn_in(window, async move |editor, cx| {
6694 while let Some(prev_task) = task {
6695 prev_task.await.log_err();
6696 task = editor
6697 .update(cx, |this, _| this.code_actions_task.take())
6698 .ok()?;
6699 }
6700
6701 editor
6702 .update(cx, |editor, cx| {
6703 editor
6704 .available_code_actions
6705 .clone()
6706 .and_then(|(location, code_actions)| {
6707 let snapshot = location.buffer.read(cx).snapshot();
6708 let point_range = location.range.to_point(&snapshot);
6709 let point_range = point_range.start.row..=point_range.end.row;
6710 if point_range.contains(&buffer_row) {
6711 Some(code_actions)
6712 } else {
6713 None
6714 }
6715 })
6716 })
6717 .ok()
6718 .flatten()
6719 })
6720 }
6721
6722 pub fn confirm_code_action(
6723 &mut self,
6724 action: &ConfirmCodeAction,
6725 window: &mut Window,
6726 cx: &mut Context<Self>,
6727 ) -> Option<Task<Result<()>>> {
6728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6729
6730 let actions_menu =
6731 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6732 menu
6733 } else {
6734 return None;
6735 };
6736
6737 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6738 let action = actions_menu.actions.get(action_ix)?;
6739 let title = action.label();
6740 let buffer = actions_menu.buffer;
6741 let workspace = self.workspace()?;
6742
6743 match action {
6744 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6745 workspace.update(cx, |workspace, cx| {
6746 workspace.schedule_resolved_task(
6747 task_source_kind,
6748 resolved_task,
6749 false,
6750 window,
6751 cx,
6752 );
6753
6754 Some(Task::ready(Ok(())))
6755 })
6756 }
6757 CodeActionsItem::CodeAction {
6758 excerpt_id,
6759 action,
6760 provider,
6761 } => {
6762 let apply_code_action =
6763 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6764 let workspace = workspace.downgrade();
6765 Some(cx.spawn_in(window, async move |editor, cx| {
6766 let project_transaction = apply_code_action.await?;
6767 Self::open_project_transaction(
6768 &editor,
6769 workspace,
6770 project_transaction,
6771 title,
6772 cx,
6773 )
6774 .await
6775 }))
6776 }
6777 CodeActionsItem::DebugScenario(scenario) => {
6778 let context = actions_menu.actions.context;
6779
6780 workspace.update(cx, |workspace, cx| {
6781 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6782 workspace.start_debug_session(
6783 scenario,
6784 context,
6785 Some(buffer),
6786 None,
6787 window,
6788 cx,
6789 );
6790 });
6791 Some(Task::ready(Ok(())))
6792 }
6793 }
6794 }
6795
6796 fn open_transaction_for_hidden_buffers(
6797 workspace: Entity<Workspace>,
6798 transaction: ProjectTransaction,
6799 title: String,
6800 window: &mut Window,
6801 cx: &mut Context<Self>,
6802 ) {
6803 if transaction.0.is_empty() {
6804 return;
6805 }
6806
6807 let edited_buffers_already_open = {
6808 let other_editors: Vec<Entity<Editor>> = workspace
6809 .read(cx)
6810 .panes()
6811 .iter()
6812 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6813 .filter(|editor| editor.entity_id() != cx.entity_id())
6814 .collect();
6815
6816 transaction.0.keys().all(|buffer| {
6817 other_editors.iter().any(|editor| {
6818 let multi_buffer = editor.read(cx).buffer();
6819 multi_buffer.read(cx).is_singleton()
6820 && multi_buffer
6821 .read(cx)
6822 .as_singleton()
6823 .map_or(false, |singleton| {
6824 singleton.entity_id() == buffer.entity_id()
6825 })
6826 })
6827 })
6828 };
6829 if !edited_buffers_already_open {
6830 let workspace = workspace.downgrade();
6831 cx.defer_in(window, move |_, window, cx| {
6832 cx.spawn_in(window, async move |editor, cx| {
6833 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6834 .await
6835 .ok()
6836 })
6837 .detach();
6838 });
6839 }
6840 }
6841
6842 pub async fn open_project_transaction(
6843 editor: &WeakEntity<Editor>,
6844 workspace: WeakEntity<Workspace>,
6845 transaction: ProjectTransaction,
6846 title: String,
6847 cx: &mut AsyncWindowContext,
6848 ) -> Result<()> {
6849 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6850 cx.update(|_, cx| {
6851 entries.sort_unstable_by_key(|(buffer, _)| {
6852 buffer.read(cx).file().map(|f| f.path().clone())
6853 });
6854 })?;
6855 if entries.is_empty() {
6856 return Ok(());
6857 }
6858
6859 // If the project transaction's edits are all contained within this editor, then
6860 // avoid opening a new editor to display them.
6861
6862 if let [(buffer, transaction)] = &*entries {
6863 let excerpt = editor.update(cx, |editor, cx| {
6864 editor
6865 .buffer()
6866 .read(cx)
6867 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6868 })?;
6869 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6870 && excerpted_buffer == *buffer
6871 {
6872 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6873 let excerpt_range = excerpt_range.to_offset(buffer);
6874 buffer
6875 .edited_ranges_for_transaction::<usize>(transaction)
6876 .all(|range| {
6877 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6878 })
6879 });
6880
6881 if all_edits_within_excerpt {
6882 return Ok(());
6883 }
6884 }
6885 }
6886
6887 let mut ranges_to_highlight = Vec::new();
6888 let excerpt_buffer = cx.new(|cx| {
6889 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6890 for (buffer_handle, transaction) in &entries {
6891 let edited_ranges = buffer_handle
6892 .read(cx)
6893 .edited_ranges_for_transaction::<Point>(transaction)
6894 .collect::<Vec<_>>();
6895 let (ranges, _) = multibuffer.set_excerpts_for_path(
6896 PathKey::for_buffer(buffer_handle, cx),
6897 buffer_handle.clone(),
6898 edited_ranges,
6899 multibuffer_context_lines(cx),
6900 cx,
6901 );
6902
6903 ranges_to_highlight.extend(ranges);
6904 }
6905 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6906 multibuffer
6907 });
6908
6909 workspace.update_in(cx, |workspace, window, cx| {
6910 let project = workspace.project().clone();
6911 let editor =
6912 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6913 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6914 editor.update(cx, |editor, cx| {
6915 editor.highlight_background::<Self>(
6916 &ranges_to_highlight,
6917 |_, theme| theme.colors().editor_highlighted_line_background,
6918 cx,
6919 );
6920 });
6921 })?;
6922
6923 Ok(())
6924 }
6925
6926 pub fn clear_code_action_providers(&mut self) {
6927 self.code_action_providers.clear();
6928 self.available_code_actions.take();
6929 }
6930
6931 pub fn add_code_action_provider(
6932 &mut self,
6933 provider: Rc<dyn CodeActionProvider>,
6934 window: &mut Window,
6935 cx: &mut Context<Self>,
6936 ) {
6937 if self
6938 .code_action_providers
6939 .iter()
6940 .any(|existing_provider| existing_provider.id() == provider.id())
6941 {
6942 return;
6943 }
6944
6945 self.code_action_providers.push(provider);
6946 self.refresh_code_actions(window, cx);
6947 }
6948
6949 pub fn remove_code_action_provider(
6950 &mut self,
6951 id: Arc<str>,
6952 window: &mut Window,
6953 cx: &mut Context<Self>,
6954 ) {
6955 self.code_action_providers
6956 .retain(|provider| provider.id() != id);
6957 self.refresh_code_actions(window, cx);
6958 }
6959
6960 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6961 !self.code_action_providers.is_empty()
6962 && EditorSettings::get_global(cx).toolbar.code_actions
6963 }
6964
6965 pub fn has_available_code_actions(&self) -> bool {
6966 self.available_code_actions
6967 .as_ref()
6968 .is_some_and(|(_, actions)| !actions.is_empty())
6969 }
6970
6971 fn render_inline_code_actions(
6972 &self,
6973 icon_size: ui::IconSize,
6974 display_row: DisplayRow,
6975 is_active: bool,
6976 cx: &mut Context<Self>,
6977 ) -> AnyElement {
6978 let show_tooltip = !self.context_menu_visible();
6979 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6980 .icon_size(icon_size)
6981 .shape(ui::IconButtonShape::Square)
6982 .icon_color(ui::Color::Hidden)
6983 .toggle_state(is_active)
6984 .when(show_tooltip, |this| {
6985 this.tooltip({
6986 let focus_handle = self.focus_handle.clone();
6987 move |_window, cx| {
6988 Tooltip::for_action_in(
6989 "Toggle Code Actions",
6990 &ToggleCodeActions {
6991 deployed_from: None,
6992 quick_launch: false,
6993 },
6994 &focus_handle,
6995 cx,
6996 )
6997 }
6998 })
6999 })
7000 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7001 window.focus(&editor.focus_handle(cx), cx);
7002 editor.toggle_code_actions(
7003 &crate::actions::ToggleCodeActions {
7004 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7005 display_row,
7006 )),
7007 quick_launch: false,
7008 },
7009 window,
7010 cx,
7011 );
7012 }))
7013 .into_any_element()
7014 }
7015
7016 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7017 &self.context_menu
7018 }
7019
7020 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7021 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7022 cx.background_executor()
7023 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7024 .await;
7025
7026 let (start_buffer, start, _, end, newest_selection) = this
7027 .update(cx, |this, cx| {
7028 let newest_selection = this.selections.newest_anchor().clone();
7029 if newest_selection.head().diff_base_anchor.is_some() {
7030 return None;
7031 }
7032 let display_snapshot = this.display_snapshot(cx);
7033 let newest_selection_adjusted =
7034 this.selections.newest_adjusted(&display_snapshot);
7035 let buffer = this.buffer.read(cx);
7036
7037 let (start_buffer, start) =
7038 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7039 let (end_buffer, end) =
7040 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7041
7042 Some((start_buffer, start, end_buffer, end, newest_selection))
7043 })?
7044 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7045 .context(
7046 "Expected selection to lie in a single buffer when refreshing code actions",
7047 )?;
7048 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7049 let providers = this.code_action_providers.clone();
7050 let tasks = this
7051 .code_action_providers
7052 .iter()
7053 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7054 .collect::<Vec<_>>();
7055 (providers, tasks)
7056 })?;
7057
7058 let mut actions = Vec::new();
7059 for (provider, provider_actions) in
7060 providers.into_iter().zip(future::join_all(tasks).await)
7061 {
7062 if let Some(provider_actions) = provider_actions.log_err() {
7063 actions.extend(provider_actions.into_iter().map(|action| {
7064 AvailableCodeAction {
7065 excerpt_id: newest_selection.start.excerpt_id,
7066 action,
7067 provider: provider.clone(),
7068 }
7069 }));
7070 }
7071 }
7072
7073 this.update(cx, |this, cx| {
7074 this.available_code_actions = if actions.is_empty() {
7075 None
7076 } else {
7077 Some((
7078 Location {
7079 buffer: start_buffer,
7080 range: start..end,
7081 },
7082 actions.into(),
7083 ))
7084 };
7085 cx.notify();
7086 })
7087 }));
7088 }
7089
7090 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7091 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7092 self.show_git_blame_inline = false;
7093
7094 self.show_git_blame_inline_delay_task =
7095 Some(cx.spawn_in(window, async move |this, cx| {
7096 cx.background_executor().timer(delay).await;
7097
7098 this.update(cx, |this, cx| {
7099 this.show_git_blame_inline = true;
7100 cx.notify();
7101 })
7102 .log_err();
7103 }));
7104 }
7105 }
7106
7107 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7108 let snapshot = self.snapshot(window, cx);
7109 let cursor = self
7110 .selections
7111 .newest::<Point>(&snapshot.display_snapshot)
7112 .head();
7113 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7114 else {
7115 return;
7116 };
7117
7118 if self.blame.is_none() {
7119 self.start_git_blame(true, window, cx);
7120 }
7121 let Some(blame) = self.blame.as_ref() else {
7122 return;
7123 };
7124
7125 let row_info = RowInfo {
7126 buffer_id: Some(buffer.remote_id()),
7127 buffer_row: Some(point.row),
7128 ..Default::default()
7129 };
7130 let Some((buffer, blame_entry)) = blame
7131 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7132 .flatten()
7133 else {
7134 return;
7135 };
7136
7137 let anchor = self.selections.newest_anchor().head();
7138 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7139 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7140 self.show_blame_popover(
7141 buffer,
7142 &blame_entry,
7143 position + last_bounds.origin,
7144 true,
7145 cx,
7146 );
7147 };
7148 }
7149
7150 fn show_blame_popover(
7151 &mut self,
7152 buffer: BufferId,
7153 blame_entry: &BlameEntry,
7154 position: gpui::Point<Pixels>,
7155 ignore_timeout: bool,
7156 cx: &mut Context<Self>,
7157 ) {
7158 if let Some(state) = &mut self.inline_blame_popover {
7159 state.hide_task.take();
7160 } else {
7161 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7162 let blame_entry = blame_entry.clone();
7163 let show_task = cx.spawn(async move |editor, cx| {
7164 if !ignore_timeout {
7165 cx.background_executor()
7166 .timer(std::time::Duration::from_millis(blame_popover_delay))
7167 .await;
7168 }
7169 editor
7170 .update(cx, |editor, cx| {
7171 editor.inline_blame_popover_show_task.take();
7172 let Some(blame) = editor.blame.as_ref() else {
7173 return;
7174 };
7175 let blame = blame.read(cx);
7176 let details = blame.details_for_entry(buffer, &blame_entry);
7177 let markdown = cx.new(|cx| {
7178 Markdown::new(
7179 details
7180 .as_ref()
7181 .map(|message| message.message.clone())
7182 .unwrap_or_default(),
7183 None,
7184 None,
7185 cx,
7186 )
7187 });
7188 editor.inline_blame_popover = Some(InlineBlamePopover {
7189 position,
7190 hide_task: None,
7191 popover_bounds: None,
7192 popover_state: InlineBlamePopoverState {
7193 scroll_handle: ScrollHandle::new(),
7194 commit_message: details,
7195 markdown,
7196 },
7197 keyboard_grace: ignore_timeout,
7198 });
7199 cx.notify();
7200 })
7201 .ok();
7202 });
7203 self.inline_blame_popover_show_task = Some(show_task);
7204 }
7205 }
7206
7207 pub fn has_mouse_context_menu(&self) -> bool {
7208 self.mouse_context_menu.is_some()
7209 }
7210
7211 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7212 self.inline_blame_popover_show_task.take();
7213 if let Some(state) = &mut self.inline_blame_popover {
7214 let hide_task = cx.spawn(async move |editor, cx| {
7215 if !ignore_timeout {
7216 cx.background_executor()
7217 .timer(std::time::Duration::from_millis(100))
7218 .await;
7219 }
7220 editor
7221 .update(cx, |editor, cx| {
7222 editor.inline_blame_popover.take();
7223 cx.notify();
7224 })
7225 .ok();
7226 });
7227 state.hide_task = Some(hide_task);
7228 true
7229 } else {
7230 false
7231 }
7232 }
7233
7234 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7235 if self.pending_rename.is_some() {
7236 return None;
7237 }
7238
7239 let provider = self.semantics_provider.clone()?;
7240 let buffer = self.buffer.read(cx);
7241 let newest_selection = self.selections.newest_anchor().clone();
7242 let cursor_position = newest_selection.head();
7243 let (cursor_buffer, cursor_buffer_position) =
7244 buffer.text_anchor_for_position(cursor_position, cx)?;
7245 let (tail_buffer, tail_buffer_position) =
7246 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7247 if cursor_buffer != tail_buffer {
7248 return None;
7249 }
7250
7251 let snapshot = cursor_buffer.read(cx).snapshot();
7252 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7253 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7254 if start_word_range != end_word_range {
7255 self.document_highlights_task.take();
7256 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7257 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7258 return None;
7259 }
7260
7261 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7262 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7263 cx.background_executor()
7264 .timer(Duration::from_millis(debounce))
7265 .await;
7266
7267 let highlights = if let Some(highlights) = cx.update(|cx| {
7268 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7269 }) {
7270 highlights.await.log_err()
7271 } else {
7272 None
7273 };
7274
7275 if let Some(highlights) = highlights {
7276 this.update(cx, |this, cx| {
7277 if this.pending_rename.is_some() {
7278 return;
7279 }
7280
7281 let buffer = this.buffer.read(cx);
7282 if buffer
7283 .text_anchor_for_position(cursor_position, cx)
7284 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7285 {
7286 return;
7287 }
7288
7289 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7290 let mut write_ranges = Vec::new();
7291 let mut read_ranges = Vec::new();
7292 for highlight in highlights {
7293 let buffer_id = cursor_buffer.read(cx).remote_id();
7294 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7295 {
7296 let start = highlight
7297 .range
7298 .start
7299 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7300 let end = highlight
7301 .range
7302 .end
7303 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7304 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7305 continue;
7306 }
7307
7308 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7309 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7310 write_ranges.push(range);
7311 } else {
7312 read_ranges.push(range);
7313 }
7314 }
7315 }
7316
7317 this.highlight_background::<DocumentHighlightRead>(
7318 &read_ranges,
7319 |_, theme| theme.colors().editor_document_highlight_read_background,
7320 cx,
7321 );
7322 this.highlight_background::<DocumentHighlightWrite>(
7323 &write_ranges,
7324 |_, theme| theme.colors().editor_document_highlight_write_background,
7325 cx,
7326 );
7327 cx.notify();
7328 })
7329 .log_err();
7330 }
7331 }));
7332 None
7333 }
7334
7335 fn prepare_highlight_query_from_selection(
7336 &mut self,
7337 window: &Window,
7338 cx: &mut Context<Editor>,
7339 ) -> Option<(String, Range<Anchor>)> {
7340 if matches!(self.mode, EditorMode::SingleLine) {
7341 return None;
7342 }
7343 if !EditorSettings::get_global(cx).selection_highlight {
7344 return None;
7345 }
7346 if self.selections.count() != 1 || self.selections.line_mode() {
7347 return None;
7348 }
7349 let snapshot = self.snapshot(window, cx);
7350 let selection = self.selections.newest::<Point>(&snapshot);
7351 // If the selection spans multiple rows OR it is empty
7352 if selection.start.row != selection.end.row
7353 || selection.start.column == selection.end.column
7354 {
7355 return None;
7356 }
7357 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7358 let query = snapshot
7359 .buffer_snapshot()
7360 .text_for_range(selection_anchor_range.clone())
7361 .collect::<String>();
7362 if query.trim().is_empty() {
7363 return None;
7364 }
7365 Some((query, selection_anchor_range))
7366 }
7367
7368 #[ztracing::instrument(skip_all)]
7369 fn update_selection_occurrence_highlights(
7370 &mut self,
7371 query_text: String,
7372 query_range: Range<Anchor>,
7373 multi_buffer_range_to_query: Range<Point>,
7374 use_debounce: bool,
7375 window: &mut Window,
7376 cx: &mut Context<Editor>,
7377 ) -> Task<()> {
7378 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7379 cx.spawn_in(window, async move |editor, cx| {
7380 if use_debounce {
7381 cx.background_executor()
7382 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7383 .await;
7384 }
7385 let match_task = cx.background_spawn(async move {
7386 let buffer_ranges = multi_buffer_snapshot
7387 .range_to_buffer_ranges(
7388 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7389 )
7390 .into_iter()
7391 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7392 let mut match_ranges = Vec::new();
7393 let Ok(regex) = project::search::SearchQuery::text(
7394 query_text.clone(),
7395 false,
7396 false,
7397 false,
7398 Default::default(),
7399 Default::default(),
7400 false,
7401 None,
7402 ) else {
7403 return Vec::default();
7404 };
7405 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7406 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7407 match_ranges.extend(
7408 regex
7409 .search(
7410 buffer_snapshot,
7411 Some(search_range.start.0..search_range.end.0),
7412 )
7413 .await
7414 .into_iter()
7415 .filter_map(|match_range| {
7416 let match_start = buffer_snapshot
7417 .anchor_after(search_range.start + match_range.start);
7418 let match_end = buffer_snapshot
7419 .anchor_before(search_range.start + match_range.end);
7420 let match_anchor_range =
7421 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7422 (match_anchor_range != query_range).then_some(match_anchor_range)
7423 }),
7424 );
7425 }
7426 match_ranges
7427 });
7428 let match_ranges = match_task.await;
7429 editor
7430 .update_in(cx, |editor, _, cx| {
7431 if use_debounce {
7432 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7433 editor.debounced_selection_highlight_complete = true;
7434 } else if editor.debounced_selection_highlight_complete {
7435 return;
7436 }
7437 if !match_ranges.is_empty() {
7438 editor.highlight_background::<SelectedTextHighlight>(
7439 &match_ranges,
7440 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7441 cx,
7442 )
7443 }
7444 })
7445 .log_err();
7446 })
7447 }
7448
7449 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7450 struct NewlineFold;
7451 let type_id = std::any::TypeId::of::<NewlineFold>();
7452 if !self.mode.is_single_line() {
7453 return;
7454 }
7455 let snapshot = self.snapshot(window, cx);
7456 if snapshot.buffer_snapshot().max_point().row == 0 {
7457 return;
7458 }
7459 let task = cx.background_spawn(async move {
7460 let new_newlines = snapshot
7461 .buffer_chars_at(MultiBufferOffset(0))
7462 .filter_map(|(c, i)| {
7463 if c == '\n' {
7464 Some(
7465 snapshot.buffer_snapshot().anchor_after(i)
7466 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7467 )
7468 } else {
7469 None
7470 }
7471 })
7472 .collect::<Vec<_>>();
7473 let existing_newlines = snapshot
7474 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7475 .filter_map(|fold| {
7476 if fold.placeholder.type_tag == Some(type_id) {
7477 Some(fold.range.start..fold.range.end)
7478 } else {
7479 None
7480 }
7481 })
7482 .collect::<Vec<_>>();
7483
7484 (new_newlines, existing_newlines)
7485 });
7486 self.folding_newlines = cx.spawn(async move |this, cx| {
7487 let (new_newlines, existing_newlines) = task.await;
7488 if new_newlines == existing_newlines {
7489 return;
7490 }
7491 let placeholder = FoldPlaceholder {
7492 render: Arc::new(move |_, _, cx| {
7493 div()
7494 .bg(cx.theme().status().hint_background)
7495 .border_b_1()
7496 .size_full()
7497 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7498 .border_color(cx.theme().status().hint)
7499 .child("\\n")
7500 .into_any()
7501 }),
7502 constrain_width: false,
7503 merge_adjacent: false,
7504 type_tag: Some(type_id),
7505 };
7506 let creases = new_newlines
7507 .into_iter()
7508 .map(|range| Crease::simple(range, placeholder.clone()))
7509 .collect();
7510 this.update(cx, |this, cx| {
7511 this.display_map.update(cx, |display_map, cx| {
7512 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7513 display_map.fold(creases, cx);
7514 });
7515 })
7516 .ok();
7517 });
7518 }
7519
7520 #[ztracing::instrument(skip_all)]
7521 fn refresh_selected_text_highlights(
7522 &mut self,
7523 on_buffer_edit: bool,
7524 window: &mut Window,
7525 cx: &mut Context<Editor>,
7526 ) {
7527 let Some((query_text, query_range)) =
7528 self.prepare_highlight_query_from_selection(window, cx)
7529 else {
7530 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7531 self.quick_selection_highlight_task.take();
7532 self.debounced_selection_highlight_task.take();
7533 self.debounced_selection_highlight_complete = false;
7534 return;
7535 };
7536 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7537 let query_changed = self
7538 .quick_selection_highlight_task
7539 .as_ref()
7540 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7541 if query_changed {
7542 self.debounced_selection_highlight_complete = false;
7543 }
7544 if on_buffer_edit || query_changed {
7545 let multi_buffer_visible_start = self
7546 .scroll_manager
7547 .anchor()
7548 .anchor
7549 .to_point(&multi_buffer_snapshot);
7550 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7551 multi_buffer_visible_start
7552 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7553 Bias::Left,
7554 );
7555 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7556 self.quick_selection_highlight_task = Some((
7557 query_range.clone(),
7558 self.update_selection_occurrence_highlights(
7559 query_text.clone(),
7560 query_range.clone(),
7561 multi_buffer_visible_range,
7562 false,
7563 window,
7564 cx,
7565 ),
7566 ));
7567 }
7568 if on_buffer_edit
7569 || self
7570 .debounced_selection_highlight_task
7571 .as_ref()
7572 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7573 {
7574 let multi_buffer_start = multi_buffer_snapshot
7575 .anchor_before(MultiBufferOffset(0))
7576 .to_point(&multi_buffer_snapshot);
7577 let multi_buffer_end = multi_buffer_snapshot
7578 .anchor_after(multi_buffer_snapshot.len())
7579 .to_point(&multi_buffer_snapshot);
7580 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7581 self.debounced_selection_highlight_task = Some((
7582 query_range.clone(),
7583 self.update_selection_occurrence_highlights(
7584 query_text,
7585 query_range,
7586 multi_buffer_full_range,
7587 true,
7588 window,
7589 cx,
7590 ),
7591 ));
7592 }
7593 }
7594
7595 pub fn refresh_edit_prediction(
7596 &mut self,
7597 debounce: bool,
7598 user_requested: bool,
7599 window: &mut Window,
7600 cx: &mut Context<Self>,
7601 ) -> Option<()> {
7602 if DisableAiSettings::get_global(cx).disable_ai {
7603 return None;
7604 }
7605
7606 let provider = self.edit_prediction_provider()?;
7607 let cursor = self.selections.newest_anchor().head();
7608 let (buffer, cursor_buffer_position) =
7609 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7610
7611 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7612 self.discard_edit_prediction(false, cx);
7613 return None;
7614 }
7615
7616 self.update_visible_edit_prediction(window, cx);
7617
7618 if !user_requested
7619 && (!self.should_show_edit_predictions()
7620 || !self.is_focused(window)
7621 || buffer.read(cx).is_empty())
7622 {
7623 self.discard_edit_prediction(false, cx);
7624 return None;
7625 }
7626
7627 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7628 Some(())
7629 }
7630
7631 fn show_edit_predictions_in_menu(&self) -> bool {
7632 match self.edit_prediction_settings {
7633 EditPredictionSettings::Disabled => false,
7634 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7635 }
7636 }
7637
7638 pub fn edit_predictions_enabled(&self) -> bool {
7639 match self.edit_prediction_settings {
7640 EditPredictionSettings::Disabled => false,
7641 EditPredictionSettings::Enabled { .. } => true,
7642 }
7643 }
7644
7645 fn edit_prediction_requires_modifier(&self) -> bool {
7646 match self.edit_prediction_settings {
7647 EditPredictionSettings::Disabled => false,
7648 EditPredictionSettings::Enabled {
7649 preview_requires_modifier,
7650 ..
7651 } => preview_requires_modifier,
7652 }
7653 }
7654
7655 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7656 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7657 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7658 self.discard_edit_prediction(false, cx);
7659 } else {
7660 let selection = self.selections.newest_anchor();
7661 let cursor = selection.head();
7662
7663 if let Some((buffer, cursor_buffer_position)) =
7664 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7665 {
7666 self.edit_prediction_settings =
7667 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7668 }
7669 }
7670 }
7671
7672 fn edit_prediction_settings_at_position(
7673 &self,
7674 buffer: &Entity<Buffer>,
7675 buffer_position: language::Anchor,
7676 cx: &App,
7677 ) -> EditPredictionSettings {
7678 if !self.mode.is_full()
7679 || !self.show_edit_predictions_override.unwrap_or(true)
7680 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7681 {
7682 return EditPredictionSettings::Disabled;
7683 }
7684
7685 let buffer = buffer.read(cx);
7686
7687 let file = buffer.file();
7688
7689 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7690 return EditPredictionSettings::Disabled;
7691 };
7692
7693 let by_provider = matches!(
7694 self.menu_edit_predictions_policy,
7695 MenuEditPredictionsPolicy::ByProvider
7696 );
7697
7698 let show_in_menu = by_provider
7699 && self
7700 .edit_prediction_provider
7701 .as_ref()
7702 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7703
7704 let preview_requires_modifier =
7705 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7706
7707 EditPredictionSettings::Enabled {
7708 show_in_menu,
7709 preview_requires_modifier,
7710 }
7711 }
7712
7713 fn should_show_edit_predictions(&self) -> bool {
7714 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7715 }
7716
7717 pub fn edit_prediction_preview_is_active(&self) -> bool {
7718 matches!(
7719 self.edit_prediction_preview,
7720 EditPredictionPreview::Active { .. }
7721 )
7722 }
7723
7724 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7725 let cursor = self.selections.newest_anchor().head();
7726 if let Some((buffer, cursor_position)) =
7727 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7728 {
7729 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7730 } else {
7731 false
7732 }
7733 }
7734
7735 pub fn supports_minimap(&self, cx: &App) -> bool {
7736 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7737 }
7738
7739 fn edit_predictions_enabled_in_buffer(
7740 &self,
7741 buffer: &Entity<Buffer>,
7742 buffer_position: language::Anchor,
7743 cx: &App,
7744 ) -> bool {
7745 maybe!({
7746 if self.read_only(cx) {
7747 return Some(false);
7748 }
7749 let provider = self.edit_prediction_provider()?;
7750 if !provider.is_enabled(buffer, buffer_position, cx) {
7751 return Some(false);
7752 }
7753 let buffer = buffer.read(cx);
7754 let Some(file) = buffer.file() else {
7755 return Some(true);
7756 };
7757 let settings = all_language_settings(Some(file), cx);
7758 Some(settings.edit_predictions_enabled_for_file(file, cx))
7759 })
7760 .unwrap_or(false)
7761 }
7762
7763 pub fn show_edit_prediction(
7764 &mut self,
7765 _: &ShowEditPrediction,
7766 window: &mut Window,
7767 cx: &mut Context<Self>,
7768 ) {
7769 if !self.has_active_edit_prediction() {
7770 self.refresh_edit_prediction(false, true, window, cx);
7771 return;
7772 }
7773
7774 self.update_visible_edit_prediction(window, cx);
7775 }
7776
7777 pub fn display_cursor_names(
7778 &mut self,
7779 _: &DisplayCursorNames,
7780 window: &mut Window,
7781 cx: &mut Context<Self>,
7782 ) {
7783 self.show_cursor_names(window, cx);
7784 }
7785
7786 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7787 self.show_cursor_names = true;
7788 cx.notify();
7789 cx.spawn_in(window, async move |this, cx| {
7790 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7791 this.update(cx, |this, cx| {
7792 this.show_cursor_names = false;
7793 cx.notify()
7794 })
7795 .ok()
7796 })
7797 .detach();
7798 }
7799
7800 pub fn accept_partial_edit_prediction(
7801 &mut self,
7802 granularity: EditPredictionGranularity,
7803 window: &mut Window,
7804 cx: &mut Context<Self>,
7805 ) {
7806 if self.show_edit_predictions_in_menu() {
7807 self.hide_context_menu(window, cx);
7808 }
7809
7810 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7811 return;
7812 };
7813
7814 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7815 return;
7816 }
7817
7818 match &active_edit_prediction.completion {
7819 EditPrediction::MoveWithin { target, .. } => {
7820 let target = *target;
7821
7822 if matches!(granularity, EditPredictionGranularity::Full) {
7823 if let Some(position_map) = &self.last_position_map {
7824 let target_row = target.to_display_point(&position_map.snapshot).row();
7825 let is_visible = position_map.visible_row_range.contains(&target_row);
7826
7827 if is_visible || !self.edit_prediction_requires_modifier() {
7828 self.unfold_ranges(&[target..target], true, false, cx);
7829 self.change_selections(
7830 SelectionEffects::scroll(Autoscroll::newest()),
7831 window,
7832 cx,
7833 |selections| {
7834 selections.select_anchor_ranges([target..target]);
7835 },
7836 );
7837 self.clear_row_highlights::<EditPredictionPreview>();
7838 self.edit_prediction_preview
7839 .set_previous_scroll_position(None);
7840 } else {
7841 // Highlight and request scroll
7842 self.edit_prediction_preview
7843 .set_previous_scroll_position(Some(
7844 position_map.snapshot.scroll_anchor,
7845 ));
7846 self.highlight_rows::<EditPredictionPreview>(
7847 target..target,
7848 cx.theme().colors().editor_highlighted_line_background,
7849 RowHighlightOptions {
7850 autoscroll: true,
7851 ..Default::default()
7852 },
7853 cx,
7854 );
7855 self.request_autoscroll(Autoscroll::fit(), cx);
7856 }
7857 }
7858 } else {
7859 self.change_selections(
7860 SelectionEffects::scroll(Autoscroll::newest()),
7861 window,
7862 cx,
7863 |selections| {
7864 selections.select_anchor_ranges([target..target]);
7865 },
7866 );
7867 }
7868 }
7869 EditPrediction::MoveOutside { snapshot, target } => {
7870 if let Some(workspace) = self.workspace() {
7871 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7872 .detach_and_log_err(cx);
7873 }
7874 }
7875 EditPrediction::Edit { edits, .. } => {
7876 self.report_edit_prediction_event(
7877 active_edit_prediction.completion_id.clone(),
7878 true,
7879 cx,
7880 );
7881
7882 match granularity {
7883 EditPredictionGranularity::Full => {
7884 if let Some(provider) = self.edit_prediction_provider() {
7885 provider.accept(cx);
7886 }
7887
7888 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7889 let snapshot = self.buffer.read(cx).snapshot(cx);
7890 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7891
7892 self.buffer.update(cx, |buffer, cx| {
7893 buffer.edit(edits.iter().cloned(), None, cx)
7894 });
7895
7896 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7897 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7898 });
7899
7900 let selections = self.selections.disjoint_anchors_arc();
7901 if let Some(transaction_id_now) =
7902 self.buffer.read(cx).last_transaction_id(cx)
7903 {
7904 if transaction_id_prev != Some(transaction_id_now) {
7905 self.selection_history
7906 .insert_transaction(transaction_id_now, selections);
7907 }
7908 }
7909
7910 self.update_visible_edit_prediction(window, cx);
7911 if self.active_edit_prediction.is_none() {
7912 self.refresh_edit_prediction(true, true, window, cx);
7913 }
7914 cx.notify();
7915 }
7916 _ => {
7917 let snapshot = self.buffer.read(cx).snapshot(cx);
7918 let cursor_offset = self
7919 .selections
7920 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7921 .head();
7922
7923 let insertion = edits.iter().find_map(|(range, text)| {
7924 let range = range.to_offset(&snapshot);
7925 if range.is_empty() && range.start == cursor_offset {
7926 Some(text)
7927 } else {
7928 None
7929 }
7930 });
7931
7932 if let Some(text) = insertion {
7933 let text_to_insert = match granularity {
7934 EditPredictionGranularity::Word => {
7935 let mut partial = text
7936 .chars()
7937 .by_ref()
7938 .take_while(|c| c.is_alphabetic())
7939 .collect::<String>();
7940 if partial.is_empty() {
7941 partial = text
7942 .chars()
7943 .by_ref()
7944 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7945 .collect::<String>();
7946 }
7947 partial
7948 }
7949 EditPredictionGranularity::Line => {
7950 if let Some(line) = text.split_inclusive('\n').next() {
7951 line.to_string()
7952 } else {
7953 text.to_string()
7954 }
7955 }
7956 EditPredictionGranularity::Full => unreachable!(),
7957 };
7958
7959 cx.emit(EditorEvent::InputHandled {
7960 utf16_range_to_replace: None,
7961 text: text_to_insert.clone().into(),
7962 });
7963
7964 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7965 self.refresh_edit_prediction(true, true, window, cx);
7966 cx.notify();
7967 } else {
7968 self.accept_partial_edit_prediction(
7969 EditPredictionGranularity::Full,
7970 window,
7971 cx,
7972 );
7973 }
7974 }
7975 }
7976 }
7977 }
7978
7979 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7980 }
7981
7982 pub fn accept_next_word_edit_prediction(
7983 &mut self,
7984 _: &AcceptNextWordEditPrediction,
7985 window: &mut Window,
7986 cx: &mut Context<Self>,
7987 ) {
7988 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7989 }
7990
7991 pub fn accept_next_line_edit_prediction(
7992 &mut self,
7993 _: &AcceptNextLineEditPrediction,
7994 window: &mut Window,
7995 cx: &mut Context<Self>,
7996 ) {
7997 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7998 }
7999
8000 pub fn accept_edit_prediction(
8001 &mut self,
8002 _: &AcceptEditPrediction,
8003 window: &mut Window,
8004 cx: &mut Context<Self>,
8005 ) {
8006 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8007 }
8008
8009 fn discard_edit_prediction(
8010 &mut self,
8011 should_report_edit_prediction_event: bool,
8012 cx: &mut Context<Self>,
8013 ) -> bool {
8014 if should_report_edit_prediction_event {
8015 let completion_id = self
8016 .active_edit_prediction
8017 .as_ref()
8018 .and_then(|active_completion| active_completion.completion_id.clone());
8019
8020 self.report_edit_prediction_event(completion_id, false, cx);
8021 }
8022
8023 if let Some(provider) = self.edit_prediction_provider() {
8024 provider.discard(cx);
8025 }
8026
8027 self.take_active_edit_prediction(cx)
8028 }
8029
8030 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8031 let Some(provider) = self.edit_prediction_provider() else {
8032 return;
8033 };
8034
8035 let Some((_, buffer, _)) = self
8036 .buffer
8037 .read(cx)
8038 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8039 else {
8040 return;
8041 };
8042
8043 let extension = buffer
8044 .read(cx)
8045 .file()
8046 .and_then(|file| Some(file.path().extension()?.to_string()));
8047
8048 let event_type = match accepted {
8049 true => "Edit Prediction Accepted",
8050 false => "Edit Prediction Discarded",
8051 };
8052 telemetry::event!(
8053 event_type,
8054 provider = provider.name(),
8055 prediction_id = id,
8056 suggestion_accepted = accepted,
8057 file_extension = extension,
8058 );
8059 }
8060
8061 fn open_editor_at_anchor(
8062 snapshot: &language::BufferSnapshot,
8063 target: language::Anchor,
8064 workspace: &Entity<Workspace>,
8065 window: &mut Window,
8066 cx: &mut App,
8067 ) -> Task<Result<()>> {
8068 workspace.update(cx, |workspace, cx| {
8069 let path = snapshot.file().map(|file| file.full_path(cx));
8070 let Some(path) =
8071 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8072 else {
8073 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8074 };
8075 let target = text::ToPoint::to_point(&target, snapshot);
8076 let item = workspace.open_path(path, None, true, window, cx);
8077 window.spawn(cx, async move |cx| {
8078 let Some(editor) = item.await?.downcast::<Editor>() else {
8079 return Ok(());
8080 };
8081 editor
8082 .update_in(cx, |editor, window, cx| {
8083 editor.go_to_singleton_buffer_point(target, window, cx);
8084 })
8085 .ok();
8086 anyhow::Ok(())
8087 })
8088 })
8089 }
8090
8091 pub fn has_active_edit_prediction(&self) -> bool {
8092 self.active_edit_prediction.is_some()
8093 }
8094
8095 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8096 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8097 return false;
8098 };
8099
8100 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8101 self.clear_highlights::<EditPredictionHighlight>(cx);
8102 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8103 true
8104 }
8105
8106 /// Returns true when we're displaying the edit prediction popover below the cursor
8107 /// like we are not previewing and the LSP autocomplete menu is visible
8108 /// or we are in `when_holding_modifier` mode.
8109 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8110 if self.edit_prediction_preview_is_active()
8111 || !self.show_edit_predictions_in_menu()
8112 || !self.edit_predictions_enabled()
8113 {
8114 return false;
8115 }
8116
8117 if self.has_visible_completions_menu() {
8118 return true;
8119 }
8120
8121 has_completion && self.edit_prediction_requires_modifier()
8122 }
8123
8124 fn handle_modifiers_changed(
8125 &mut self,
8126 modifiers: Modifiers,
8127 position_map: &PositionMap,
8128 window: &mut Window,
8129 cx: &mut Context<Self>,
8130 ) {
8131 // Ensure that the edit prediction preview is updated, even when not
8132 // enabled, if there's an active edit prediction preview.
8133 if self.show_edit_predictions_in_menu()
8134 || matches!(
8135 self.edit_prediction_preview,
8136 EditPredictionPreview::Active { .. }
8137 )
8138 {
8139 self.update_edit_prediction_preview(&modifiers, window, cx);
8140 }
8141
8142 self.update_selection_mode(&modifiers, position_map, window, cx);
8143
8144 let mouse_position = window.mouse_position();
8145 if !position_map.text_hitbox.is_hovered(window) {
8146 return;
8147 }
8148
8149 self.update_hovered_link(
8150 position_map.point_for_position(mouse_position),
8151 &position_map.snapshot,
8152 modifiers,
8153 window,
8154 cx,
8155 )
8156 }
8157
8158 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8159 match EditorSettings::get_global(cx).multi_cursor_modifier {
8160 MultiCursorModifier::Alt => modifiers.secondary(),
8161 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8162 }
8163 }
8164
8165 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8166 match EditorSettings::get_global(cx).multi_cursor_modifier {
8167 MultiCursorModifier::Alt => modifiers.alt,
8168 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8169 }
8170 }
8171
8172 fn columnar_selection_mode(
8173 modifiers: &Modifiers,
8174 cx: &mut Context<Self>,
8175 ) -> Option<ColumnarMode> {
8176 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8177 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8178 Some(ColumnarMode::FromMouse)
8179 } else if Self::is_alt_pressed(modifiers, cx) {
8180 Some(ColumnarMode::FromSelection)
8181 } else {
8182 None
8183 }
8184 } else {
8185 None
8186 }
8187 }
8188
8189 fn update_selection_mode(
8190 &mut self,
8191 modifiers: &Modifiers,
8192 position_map: &PositionMap,
8193 window: &mut Window,
8194 cx: &mut Context<Self>,
8195 ) {
8196 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8197 return;
8198 };
8199 if self.selections.pending_anchor().is_none() {
8200 return;
8201 }
8202
8203 let mouse_position = window.mouse_position();
8204 let point_for_position = position_map.point_for_position(mouse_position);
8205 let position = point_for_position.previous_valid;
8206
8207 self.select(
8208 SelectPhase::BeginColumnar {
8209 position,
8210 reset: false,
8211 mode,
8212 goal_column: point_for_position.exact_unclipped.column(),
8213 },
8214 window,
8215 cx,
8216 );
8217 }
8218
8219 fn update_edit_prediction_preview(
8220 &mut self,
8221 modifiers: &Modifiers,
8222 window: &mut Window,
8223 cx: &mut Context<Self>,
8224 ) {
8225 let mut modifiers_held = false;
8226
8227 // Check bindings for all granularities.
8228 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8229 let granularities = [
8230 EditPredictionGranularity::Full,
8231 EditPredictionGranularity::Line,
8232 EditPredictionGranularity::Word,
8233 ];
8234
8235 for granularity in granularities {
8236 if let Some(keystroke) = self
8237 .accept_edit_prediction_keybind(granularity, window, cx)
8238 .keystroke()
8239 {
8240 modifiers_held = modifiers_held
8241 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8242 }
8243 }
8244
8245 if modifiers_held {
8246 if matches!(
8247 self.edit_prediction_preview,
8248 EditPredictionPreview::Inactive { .. }
8249 ) {
8250 self.edit_prediction_preview = EditPredictionPreview::Active {
8251 previous_scroll_position: None,
8252 since: Instant::now(),
8253 };
8254
8255 self.update_visible_edit_prediction(window, cx);
8256 cx.notify();
8257 }
8258 } else if let EditPredictionPreview::Active {
8259 previous_scroll_position,
8260 since,
8261 } = self.edit_prediction_preview
8262 {
8263 if let (Some(previous_scroll_position), Some(position_map)) =
8264 (previous_scroll_position, self.last_position_map.as_ref())
8265 {
8266 self.set_scroll_position(
8267 previous_scroll_position
8268 .scroll_position(&position_map.snapshot.display_snapshot),
8269 window,
8270 cx,
8271 );
8272 }
8273
8274 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8275 released_too_fast: since.elapsed() < Duration::from_millis(200),
8276 };
8277 self.clear_row_highlights::<EditPredictionPreview>();
8278 self.update_visible_edit_prediction(window, cx);
8279 cx.notify();
8280 }
8281 }
8282
8283 fn update_visible_edit_prediction(
8284 &mut self,
8285 _window: &mut Window,
8286 cx: &mut Context<Self>,
8287 ) -> Option<()> {
8288 if DisableAiSettings::get_global(cx).disable_ai {
8289 return None;
8290 }
8291
8292 if self.ime_transaction.is_some() {
8293 self.discard_edit_prediction(false, cx);
8294 return None;
8295 }
8296
8297 let selection = self.selections.newest_anchor();
8298 let cursor = selection.head();
8299 let multibuffer = self.buffer.read(cx).snapshot(cx);
8300 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8301 let excerpt_id = cursor.excerpt_id;
8302
8303 let show_in_menu = self.show_edit_predictions_in_menu();
8304 let completions_menu_has_precedence = !show_in_menu
8305 && (self.context_menu.borrow().is_some()
8306 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8307
8308 if completions_menu_has_precedence
8309 || !offset_selection.is_empty()
8310 || self
8311 .active_edit_prediction
8312 .as_ref()
8313 .is_some_and(|completion| {
8314 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8315 return false;
8316 };
8317 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8318 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8319 !invalidation_range.contains(&offset_selection.head())
8320 })
8321 {
8322 self.discard_edit_prediction(false, cx);
8323 return None;
8324 }
8325
8326 self.take_active_edit_prediction(cx);
8327 let Some(provider) = self.edit_prediction_provider() else {
8328 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8329 return None;
8330 };
8331
8332 let (buffer, cursor_buffer_position) =
8333 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8334
8335 self.edit_prediction_settings =
8336 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8337
8338 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8339
8340 if self.edit_prediction_indent_conflict {
8341 let cursor_point = cursor.to_point(&multibuffer);
8342 let mut suggested_indent = None;
8343 multibuffer.suggested_indents_callback(
8344 cursor_point.row..cursor_point.row + 1,
8345 |_, indent| {
8346 suggested_indent = Some(indent);
8347 ControlFlow::Break(())
8348 },
8349 cx,
8350 );
8351
8352 if let Some(indent) = suggested_indent
8353 && indent.len == cursor_point.column
8354 {
8355 self.edit_prediction_indent_conflict = false;
8356 }
8357 }
8358
8359 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8360
8361 let (completion_id, edits, edit_preview) = match edit_prediction {
8362 edit_prediction_types::EditPrediction::Local {
8363 id,
8364 edits,
8365 edit_preview,
8366 } => (id, edits, edit_preview),
8367 edit_prediction_types::EditPrediction::Jump {
8368 id,
8369 snapshot,
8370 target,
8371 } => {
8372 if let Some(provider) = &self.edit_prediction_provider {
8373 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8374 }
8375 self.stale_edit_prediction_in_menu = None;
8376 self.active_edit_prediction = Some(EditPredictionState {
8377 inlay_ids: vec![],
8378 completion: EditPrediction::MoveOutside { snapshot, target },
8379 completion_id: id,
8380 invalidation_range: None,
8381 });
8382 cx.notify();
8383 return Some(());
8384 }
8385 };
8386
8387 let edits = edits
8388 .into_iter()
8389 .flat_map(|(range, new_text)| {
8390 Some((
8391 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8392 new_text,
8393 ))
8394 })
8395 .collect::<Vec<_>>();
8396 if edits.is_empty() {
8397 return None;
8398 }
8399
8400 let first_edit_start = edits.first().unwrap().0.start;
8401 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8402 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8403
8404 let last_edit_end = edits.last().unwrap().0.end;
8405 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8406 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8407
8408 let cursor_row = cursor.to_point(&multibuffer).row;
8409
8410 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8411
8412 let mut inlay_ids = Vec::new();
8413 let invalidation_row_range;
8414 let move_invalidation_row_range = if cursor_row < edit_start_row {
8415 Some(cursor_row..edit_end_row)
8416 } else if cursor_row > edit_end_row {
8417 Some(edit_start_row..cursor_row)
8418 } else {
8419 None
8420 };
8421 let supports_jump = self
8422 .edit_prediction_provider
8423 .as_ref()
8424 .map(|provider| provider.provider.supports_jump_to_edit())
8425 .unwrap_or(true);
8426
8427 let is_move = supports_jump
8428 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8429 let completion = if is_move {
8430 if let Some(provider) = &self.edit_prediction_provider {
8431 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8432 }
8433 invalidation_row_range =
8434 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8435 let target = first_edit_start;
8436 EditPrediction::MoveWithin { target, snapshot }
8437 } else {
8438 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8439 && !self.edit_predictions_hidden_for_vim_mode;
8440
8441 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8442 if provider.show_tab_accept_marker() {
8443 EditDisplayMode::TabAccept
8444 } else {
8445 EditDisplayMode::Inline
8446 }
8447 } else {
8448 EditDisplayMode::DiffPopover
8449 };
8450
8451 if show_completions_in_buffer {
8452 if let Some(provider) = &self.edit_prediction_provider {
8453 let suggestion_display_type = match display_mode {
8454 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8455 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8456 SuggestionDisplayType::GhostText
8457 }
8458 };
8459 provider.provider.did_show(suggestion_display_type, cx);
8460 }
8461 if edits
8462 .iter()
8463 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8464 {
8465 let mut inlays = Vec::new();
8466 for (range, new_text) in &edits {
8467 let inlay = Inlay::edit_prediction(
8468 post_inc(&mut self.next_inlay_id),
8469 range.start,
8470 new_text.as_ref(),
8471 );
8472 inlay_ids.push(inlay.id);
8473 inlays.push(inlay);
8474 }
8475
8476 self.splice_inlays(&[], inlays, cx);
8477 } else {
8478 let background_color = cx.theme().status().deleted_background;
8479 self.highlight_text::<EditPredictionHighlight>(
8480 edits.iter().map(|(range, _)| range.clone()).collect(),
8481 HighlightStyle {
8482 background_color: Some(background_color),
8483 ..Default::default()
8484 },
8485 cx,
8486 );
8487 }
8488 }
8489
8490 invalidation_row_range = edit_start_row..edit_end_row;
8491
8492 EditPrediction::Edit {
8493 edits,
8494 edit_preview,
8495 display_mode,
8496 snapshot,
8497 }
8498 };
8499
8500 let invalidation_range = multibuffer
8501 .anchor_before(Point::new(invalidation_row_range.start, 0))
8502 ..multibuffer.anchor_after(Point::new(
8503 invalidation_row_range.end,
8504 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8505 ));
8506
8507 self.stale_edit_prediction_in_menu = None;
8508 self.active_edit_prediction = Some(EditPredictionState {
8509 inlay_ids,
8510 completion,
8511 completion_id,
8512 invalidation_range: Some(invalidation_range),
8513 });
8514
8515 cx.notify();
8516
8517 Some(())
8518 }
8519
8520 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8521 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8522 }
8523
8524 fn clear_tasks(&mut self) {
8525 self.tasks.clear()
8526 }
8527
8528 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8529 if self.tasks.insert(key, value).is_some() {
8530 // This case should hopefully be rare, but just in case...
8531 log::error!(
8532 "multiple different run targets found on a single line, only the last target will be rendered"
8533 )
8534 }
8535 }
8536
8537 /// Get all display points of breakpoints that will be rendered within editor
8538 ///
8539 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8540 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8541 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8542 fn active_breakpoints(
8543 &self,
8544 range: Range<DisplayRow>,
8545 window: &mut Window,
8546 cx: &mut Context<Self>,
8547 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8548 let mut breakpoint_display_points = HashMap::default();
8549
8550 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8551 return breakpoint_display_points;
8552 };
8553
8554 let snapshot = self.snapshot(window, cx);
8555
8556 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8557 let Some(project) = self.project() else {
8558 return breakpoint_display_points;
8559 };
8560
8561 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8562 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8563
8564 for (buffer_snapshot, range, excerpt_id) in
8565 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8566 {
8567 let Some(buffer) = project
8568 .read(cx)
8569 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8570 else {
8571 continue;
8572 };
8573 let breakpoints = breakpoint_store.read(cx).breakpoints(
8574 &buffer,
8575 Some(
8576 buffer_snapshot.anchor_before(range.start)
8577 ..buffer_snapshot.anchor_after(range.end),
8578 ),
8579 buffer_snapshot,
8580 cx,
8581 );
8582 for (breakpoint, state) in breakpoints {
8583 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8584 let position = multi_buffer_anchor
8585 .to_point(&multi_buffer_snapshot)
8586 .to_display_point(&snapshot);
8587
8588 breakpoint_display_points.insert(
8589 position.row(),
8590 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8591 );
8592 }
8593 }
8594
8595 breakpoint_display_points
8596 }
8597
8598 fn breakpoint_context_menu(
8599 &self,
8600 anchor: Anchor,
8601 window: &mut Window,
8602 cx: &mut Context<Self>,
8603 ) -> Entity<ui::ContextMenu> {
8604 let weak_editor = cx.weak_entity();
8605 let focus_handle = self.focus_handle(cx);
8606
8607 let row = self
8608 .buffer
8609 .read(cx)
8610 .snapshot(cx)
8611 .summary_for_anchor::<Point>(&anchor)
8612 .row;
8613
8614 let breakpoint = self
8615 .breakpoint_at_row(row, window, cx)
8616 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8617
8618 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8619 "Edit Log Breakpoint"
8620 } else {
8621 "Set Log Breakpoint"
8622 };
8623
8624 let condition_breakpoint_msg = if breakpoint
8625 .as_ref()
8626 .is_some_and(|bp| bp.1.condition.is_some())
8627 {
8628 "Edit Condition Breakpoint"
8629 } else {
8630 "Set Condition Breakpoint"
8631 };
8632
8633 let hit_condition_breakpoint_msg = if breakpoint
8634 .as_ref()
8635 .is_some_and(|bp| bp.1.hit_condition.is_some())
8636 {
8637 "Edit Hit Condition Breakpoint"
8638 } else {
8639 "Set Hit Condition Breakpoint"
8640 };
8641
8642 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8643 "Unset Breakpoint"
8644 } else {
8645 "Set Breakpoint"
8646 };
8647
8648 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8649
8650 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8651 BreakpointState::Enabled => Some("Disable"),
8652 BreakpointState::Disabled => Some("Enable"),
8653 });
8654
8655 let (anchor, breakpoint) =
8656 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8657
8658 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8659 menu.on_blur_subscription(Subscription::new(|| {}))
8660 .context(focus_handle)
8661 .when(run_to_cursor, |this| {
8662 let weak_editor = weak_editor.clone();
8663 this.entry("Run to cursor", None, move |window, cx| {
8664 weak_editor
8665 .update(cx, |editor, cx| {
8666 editor.change_selections(
8667 SelectionEffects::no_scroll(),
8668 window,
8669 cx,
8670 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8671 );
8672 })
8673 .ok();
8674
8675 window.dispatch_action(Box::new(RunToCursor), cx);
8676 })
8677 .separator()
8678 })
8679 .when_some(toggle_state_msg, |this, msg| {
8680 this.entry(msg, None, {
8681 let weak_editor = weak_editor.clone();
8682 let breakpoint = breakpoint.clone();
8683 move |_window, cx| {
8684 weak_editor
8685 .update(cx, |this, cx| {
8686 this.edit_breakpoint_at_anchor(
8687 anchor,
8688 breakpoint.as_ref().clone(),
8689 BreakpointEditAction::InvertState,
8690 cx,
8691 );
8692 })
8693 .log_err();
8694 }
8695 })
8696 })
8697 .entry(set_breakpoint_msg, None, {
8698 let weak_editor = weak_editor.clone();
8699 let breakpoint = breakpoint.clone();
8700 move |_window, cx| {
8701 weak_editor
8702 .update(cx, |this, cx| {
8703 this.edit_breakpoint_at_anchor(
8704 anchor,
8705 breakpoint.as_ref().clone(),
8706 BreakpointEditAction::Toggle,
8707 cx,
8708 );
8709 })
8710 .log_err();
8711 }
8712 })
8713 .entry(log_breakpoint_msg, None, {
8714 let breakpoint = breakpoint.clone();
8715 let weak_editor = weak_editor.clone();
8716 move |window, cx| {
8717 weak_editor
8718 .update(cx, |this, cx| {
8719 this.add_edit_breakpoint_block(
8720 anchor,
8721 breakpoint.as_ref(),
8722 BreakpointPromptEditAction::Log,
8723 window,
8724 cx,
8725 );
8726 })
8727 .log_err();
8728 }
8729 })
8730 .entry(condition_breakpoint_msg, None, {
8731 let breakpoint = breakpoint.clone();
8732 let weak_editor = weak_editor.clone();
8733 move |window, cx| {
8734 weak_editor
8735 .update(cx, |this, cx| {
8736 this.add_edit_breakpoint_block(
8737 anchor,
8738 breakpoint.as_ref(),
8739 BreakpointPromptEditAction::Condition,
8740 window,
8741 cx,
8742 );
8743 })
8744 .log_err();
8745 }
8746 })
8747 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8748 weak_editor
8749 .update(cx, |this, cx| {
8750 this.add_edit_breakpoint_block(
8751 anchor,
8752 breakpoint.as_ref(),
8753 BreakpointPromptEditAction::HitCondition,
8754 window,
8755 cx,
8756 );
8757 })
8758 .log_err();
8759 })
8760 })
8761 }
8762
8763 fn render_breakpoint(
8764 &self,
8765 position: Anchor,
8766 row: DisplayRow,
8767 breakpoint: &Breakpoint,
8768 state: Option<BreakpointSessionState>,
8769 cx: &mut Context<Self>,
8770 ) -> IconButton {
8771 let is_rejected = state.is_some_and(|s| !s.verified);
8772 // Is it a breakpoint that shows up when hovering over gutter?
8773 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8774 (false, false),
8775 |PhantomBreakpointIndicator {
8776 is_active,
8777 display_row,
8778 collides_with_existing_breakpoint,
8779 }| {
8780 (
8781 is_active && display_row == row,
8782 collides_with_existing_breakpoint,
8783 )
8784 },
8785 );
8786
8787 let (color, icon) = {
8788 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8789 (false, false) => ui::IconName::DebugBreakpoint,
8790 (true, false) => ui::IconName::DebugLogBreakpoint,
8791 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8792 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8793 };
8794
8795 let theme_colors = cx.theme().colors();
8796
8797 let color = if is_phantom {
8798 if collides_with_existing {
8799 Color::Custom(
8800 theme_colors
8801 .debugger_accent
8802 .blend(theme_colors.text.opacity(0.6)),
8803 )
8804 } else {
8805 Color::Hint
8806 }
8807 } else if is_rejected {
8808 Color::Disabled
8809 } else {
8810 Color::Debugger
8811 };
8812
8813 (color, icon)
8814 };
8815
8816 let breakpoint = Arc::from(breakpoint.clone());
8817
8818 let alt_as_text = gpui::Keystroke {
8819 modifiers: Modifiers::secondary_key(),
8820 ..Default::default()
8821 };
8822 let primary_action_text = if breakpoint.is_disabled() {
8823 "Enable breakpoint"
8824 } else if is_phantom && !collides_with_existing {
8825 "Set breakpoint"
8826 } else {
8827 "Unset breakpoint"
8828 };
8829 let focus_handle = self.focus_handle.clone();
8830
8831 let meta = if is_rejected {
8832 SharedString::from("No executable code is associated with this line.")
8833 } else if collides_with_existing && !breakpoint.is_disabled() {
8834 SharedString::from(format!(
8835 "{alt_as_text}-click to disable,\nright-click for more options."
8836 ))
8837 } else {
8838 SharedString::from("Right-click for more options.")
8839 };
8840 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8841 .icon_size(IconSize::XSmall)
8842 .size(ui::ButtonSize::None)
8843 .when(is_rejected, |this| {
8844 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8845 })
8846 .icon_color(color)
8847 .style(ButtonStyle::Transparent)
8848 .on_click(cx.listener({
8849 move |editor, event: &ClickEvent, window, cx| {
8850 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8851 BreakpointEditAction::InvertState
8852 } else {
8853 BreakpointEditAction::Toggle
8854 };
8855
8856 window.focus(&editor.focus_handle(cx), cx);
8857 editor.edit_breakpoint_at_anchor(
8858 position,
8859 breakpoint.as_ref().clone(),
8860 edit_action,
8861 cx,
8862 );
8863 }
8864 }))
8865 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8866 editor.set_breakpoint_context_menu(
8867 row,
8868 Some(position),
8869 event.position(),
8870 window,
8871 cx,
8872 );
8873 }))
8874 .tooltip(move |_window, cx| {
8875 Tooltip::with_meta_in(
8876 primary_action_text,
8877 Some(&ToggleBreakpoint),
8878 meta.clone(),
8879 &focus_handle,
8880 cx,
8881 )
8882 })
8883 }
8884
8885 fn build_tasks_context(
8886 project: &Entity<Project>,
8887 buffer: &Entity<Buffer>,
8888 buffer_row: u32,
8889 tasks: &Arc<RunnableTasks>,
8890 cx: &mut Context<Self>,
8891 ) -> Task<Option<task::TaskContext>> {
8892 let position = Point::new(buffer_row, tasks.column);
8893 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8894 let location = Location {
8895 buffer: buffer.clone(),
8896 range: range_start..range_start,
8897 };
8898 // Fill in the environmental variables from the tree-sitter captures
8899 let mut captured_task_variables = TaskVariables::default();
8900 for (capture_name, value) in tasks.extra_variables.clone() {
8901 captured_task_variables.insert(
8902 task::VariableName::Custom(capture_name.into()),
8903 value.clone(),
8904 );
8905 }
8906 project.update(cx, |project, cx| {
8907 project.task_store().update(cx, |task_store, cx| {
8908 task_store.task_context_for_location(captured_task_variables, location, cx)
8909 })
8910 })
8911 }
8912
8913 pub fn spawn_nearest_task(
8914 &mut self,
8915 action: &SpawnNearestTask,
8916 window: &mut Window,
8917 cx: &mut Context<Self>,
8918 ) {
8919 let Some((workspace, _)) = self.workspace.clone() else {
8920 return;
8921 };
8922 let Some(project) = self.project.clone() else {
8923 return;
8924 };
8925
8926 // Try to find a closest, enclosing node using tree-sitter that has a task
8927 let Some((buffer, buffer_row, tasks)) = self
8928 .find_enclosing_node_task(cx)
8929 // Or find the task that's closest in row-distance.
8930 .or_else(|| self.find_closest_task(cx))
8931 else {
8932 return;
8933 };
8934
8935 let reveal_strategy = action.reveal;
8936 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8937 cx.spawn_in(window, async move |_, cx| {
8938 let context = task_context.await?;
8939 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8940
8941 let resolved = &mut resolved_task.resolved;
8942 resolved.reveal = reveal_strategy;
8943
8944 workspace
8945 .update_in(cx, |workspace, window, cx| {
8946 workspace.schedule_resolved_task(
8947 task_source_kind,
8948 resolved_task,
8949 false,
8950 window,
8951 cx,
8952 );
8953 })
8954 .ok()
8955 })
8956 .detach();
8957 }
8958
8959 fn find_closest_task(
8960 &mut self,
8961 cx: &mut Context<Self>,
8962 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8963 let cursor_row = self
8964 .selections
8965 .newest_adjusted(&self.display_snapshot(cx))
8966 .head()
8967 .row;
8968
8969 let ((buffer_id, row), tasks) = self
8970 .tasks
8971 .iter()
8972 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8973
8974 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8975 let tasks = Arc::new(tasks.to_owned());
8976 Some((buffer, *row, tasks))
8977 }
8978
8979 fn find_enclosing_node_task(
8980 &mut self,
8981 cx: &mut Context<Self>,
8982 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8983 let snapshot = self.buffer.read(cx).snapshot(cx);
8984 let offset = self
8985 .selections
8986 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8987 .head();
8988 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8989 let offset = excerpt.map_offset_to_buffer(offset);
8990 let buffer_id = excerpt.buffer().remote_id();
8991
8992 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8993 let mut cursor = layer.node().walk();
8994
8995 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8996 if cursor.node().end_byte() == offset.0 {
8997 cursor.goto_next_sibling();
8998 }
8999 }
9000
9001 // Ascend to the smallest ancestor that contains the range and has a task.
9002 loop {
9003 let node = cursor.node();
9004 let node_range = node.byte_range();
9005 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9006
9007 // Check if this node contains our offset
9008 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9009 // If it contains offset, check for task
9010 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9011 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9012 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9013 }
9014 }
9015
9016 if !cursor.goto_parent() {
9017 break;
9018 }
9019 }
9020 None
9021 }
9022
9023 fn render_run_indicator(
9024 &self,
9025 _style: &EditorStyle,
9026 is_active: bool,
9027 row: DisplayRow,
9028 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9029 cx: &mut Context<Self>,
9030 ) -> IconButton {
9031 let color = Color::Muted;
9032 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9033
9034 IconButton::new(
9035 ("run_indicator", row.0 as usize),
9036 ui::IconName::PlayOutlined,
9037 )
9038 .shape(ui::IconButtonShape::Square)
9039 .icon_size(IconSize::XSmall)
9040 .icon_color(color)
9041 .toggle_state(is_active)
9042 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9043 let quick_launch = match e {
9044 ClickEvent::Keyboard(_) => true,
9045 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9046 };
9047
9048 window.focus(&editor.focus_handle(cx), cx);
9049 editor.toggle_code_actions(
9050 &ToggleCodeActions {
9051 deployed_from: Some(CodeActionSource::RunMenu(row)),
9052 quick_launch,
9053 },
9054 window,
9055 cx,
9056 );
9057 }))
9058 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9059 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9060 }))
9061 }
9062
9063 pub fn context_menu_visible(&self) -> bool {
9064 !self.edit_prediction_preview_is_active()
9065 && self
9066 .context_menu
9067 .borrow()
9068 .as_ref()
9069 .is_some_and(|menu| menu.visible())
9070 }
9071
9072 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9073 self.context_menu
9074 .borrow()
9075 .as_ref()
9076 .map(|menu| menu.origin())
9077 }
9078
9079 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9080 self.context_menu_options = Some(options);
9081 }
9082
9083 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9084 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9085
9086 fn render_edit_prediction_popover(
9087 &mut self,
9088 text_bounds: &Bounds<Pixels>,
9089 content_origin: gpui::Point<Pixels>,
9090 right_margin: Pixels,
9091 editor_snapshot: &EditorSnapshot,
9092 visible_row_range: Range<DisplayRow>,
9093 scroll_top: ScrollOffset,
9094 scroll_bottom: ScrollOffset,
9095 line_layouts: &[LineWithInvisibles],
9096 line_height: Pixels,
9097 scroll_position: gpui::Point<ScrollOffset>,
9098 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9099 newest_selection_head: Option<DisplayPoint>,
9100 editor_width: Pixels,
9101 style: &EditorStyle,
9102 window: &mut Window,
9103 cx: &mut App,
9104 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9105 if self.mode().is_minimap() {
9106 return None;
9107 }
9108 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9109
9110 if self.edit_prediction_visible_in_cursor_popover(true) {
9111 return None;
9112 }
9113
9114 match &active_edit_prediction.completion {
9115 EditPrediction::MoveWithin { target, .. } => {
9116 let target_display_point = target.to_display_point(editor_snapshot);
9117
9118 if self.edit_prediction_requires_modifier() {
9119 if !self.edit_prediction_preview_is_active() {
9120 return None;
9121 }
9122
9123 self.render_edit_prediction_modifier_jump_popover(
9124 text_bounds,
9125 content_origin,
9126 visible_row_range,
9127 line_layouts,
9128 line_height,
9129 scroll_pixel_position,
9130 newest_selection_head,
9131 target_display_point,
9132 window,
9133 cx,
9134 )
9135 } else {
9136 self.render_edit_prediction_eager_jump_popover(
9137 text_bounds,
9138 content_origin,
9139 editor_snapshot,
9140 visible_row_range,
9141 scroll_top,
9142 scroll_bottom,
9143 line_height,
9144 scroll_pixel_position,
9145 target_display_point,
9146 editor_width,
9147 window,
9148 cx,
9149 )
9150 }
9151 }
9152 EditPrediction::Edit {
9153 display_mode: EditDisplayMode::Inline,
9154 ..
9155 } => None,
9156 EditPrediction::Edit {
9157 display_mode: EditDisplayMode::TabAccept,
9158 edits,
9159 ..
9160 } => {
9161 let range = &edits.first()?.0;
9162 let target_display_point = range.end.to_display_point(editor_snapshot);
9163
9164 self.render_edit_prediction_end_of_line_popover(
9165 "Accept",
9166 editor_snapshot,
9167 visible_row_range,
9168 target_display_point,
9169 line_height,
9170 scroll_pixel_position,
9171 content_origin,
9172 editor_width,
9173 window,
9174 cx,
9175 )
9176 }
9177 EditPrediction::Edit {
9178 edits,
9179 edit_preview,
9180 display_mode: EditDisplayMode::DiffPopover,
9181 snapshot,
9182 } => self.render_edit_prediction_diff_popover(
9183 text_bounds,
9184 content_origin,
9185 right_margin,
9186 editor_snapshot,
9187 visible_row_range,
9188 line_layouts,
9189 line_height,
9190 scroll_position,
9191 scroll_pixel_position,
9192 newest_selection_head,
9193 editor_width,
9194 style,
9195 edits,
9196 edit_preview,
9197 snapshot,
9198 window,
9199 cx,
9200 ),
9201 EditPrediction::MoveOutside { snapshot, .. } => {
9202 let mut element = self
9203 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9204 .into_any();
9205
9206 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9207 let origin_x = text_bounds.size.width - size.width - px(30.);
9208 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9209 element.prepaint_at(origin, window, cx);
9210
9211 Some((element, origin))
9212 }
9213 }
9214 }
9215
9216 fn render_edit_prediction_modifier_jump_popover(
9217 &mut self,
9218 text_bounds: &Bounds<Pixels>,
9219 content_origin: gpui::Point<Pixels>,
9220 visible_row_range: Range<DisplayRow>,
9221 line_layouts: &[LineWithInvisibles],
9222 line_height: Pixels,
9223 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9224 newest_selection_head: Option<DisplayPoint>,
9225 target_display_point: DisplayPoint,
9226 window: &mut Window,
9227 cx: &mut App,
9228 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9229 let scrolled_content_origin =
9230 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9231
9232 const SCROLL_PADDING_Y: Pixels = px(12.);
9233
9234 if target_display_point.row() < visible_row_range.start {
9235 return self.render_edit_prediction_scroll_popover(
9236 |_| SCROLL_PADDING_Y,
9237 IconName::ArrowUp,
9238 visible_row_range,
9239 line_layouts,
9240 newest_selection_head,
9241 scrolled_content_origin,
9242 window,
9243 cx,
9244 );
9245 } else if target_display_point.row() >= visible_row_range.end {
9246 return self.render_edit_prediction_scroll_popover(
9247 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9248 IconName::ArrowDown,
9249 visible_row_range,
9250 line_layouts,
9251 newest_selection_head,
9252 scrolled_content_origin,
9253 window,
9254 cx,
9255 );
9256 }
9257
9258 const POLE_WIDTH: Pixels = px(2.);
9259
9260 let line_layout =
9261 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9262 let target_column = target_display_point.column() as usize;
9263
9264 let target_x = line_layout.x_for_index(target_column);
9265 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9266 - scroll_pixel_position.y;
9267
9268 let flag_on_right = target_x < text_bounds.size.width / 2.;
9269
9270 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9271 border_color.l += 0.001;
9272
9273 let mut element = v_flex()
9274 .items_end()
9275 .when(flag_on_right, |el| el.items_start())
9276 .child(if flag_on_right {
9277 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9278 .rounded_bl(px(0.))
9279 .rounded_tl(px(0.))
9280 .border_l_2()
9281 .border_color(border_color)
9282 } else {
9283 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9284 .rounded_br(px(0.))
9285 .rounded_tr(px(0.))
9286 .border_r_2()
9287 .border_color(border_color)
9288 })
9289 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9290 .into_any();
9291
9292 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9293
9294 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9295 - point(
9296 if flag_on_right {
9297 POLE_WIDTH
9298 } else {
9299 size.width - POLE_WIDTH
9300 },
9301 size.height - line_height,
9302 );
9303
9304 origin.x = origin.x.max(content_origin.x);
9305
9306 element.prepaint_at(origin, window, cx);
9307
9308 Some((element, origin))
9309 }
9310
9311 fn render_edit_prediction_scroll_popover(
9312 &mut self,
9313 to_y: impl Fn(Size<Pixels>) -> Pixels,
9314 scroll_icon: IconName,
9315 visible_row_range: Range<DisplayRow>,
9316 line_layouts: &[LineWithInvisibles],
9317 newest_selection_head: Option<DisplayPoint>,
9318 scrolled_content_origin: gpui::Point<Pixels>,
9319 window: &mut Window,
9320 cx: &mut App,
9321 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9322 let mut element = self
9323 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9324 .into_any();
9325
9326 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9327
9328 let cursor = newest_selection_head?;
9329 let cursor_row_layout =
9330 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9331 let cursor_column = cursor.column() as usize;
9332
9333 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9334
9335 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9336
9337 element.prepaint_at(origin, window, cx);
9338 Some((element, origin))
9339 }
9340
9341 fn render_edit_prediction_eager_jump_popover(
9342 &mut self,
9343 text_bounds: &Bounds<Pixels>,
9344 content_origin: gpui::Point<Pixels>,
9345 editor_snapshot: &EditorSnapshot,
9346 visible_row_range: Range<DisplayRow>,
9347 scroll_top: ScrollOffset,
9348 scroll_bottom: ScrollOffset,
9349 line_height: Pixels,
9350 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9351 target_display_point: DisplayPoint,
9352 editor_width: Pixels,
9353 window: &mut Window,
9354 cx: &mut App,
9355 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9356 if target_display_point.row().as_f64() < scroll_top {
9357 let mut element = self
9358 .render_edit_prediction_line_popover(
9359 "Jump to Edit",
9360 Some(IconName::ArrowUp),
9361 window,
9362 cx,
9363 )
9364 .into_any();
9365
9366 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9367 let offset = point(
9368 (text_bounds.size.width - size.width) / 2.,
9369 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9370 );
9371
9372 let origin = text_bounds.origin + offset;
9373 element.prepaint_at(origin, window, cx);
9374 Some((element, origin))
9375 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9376 let mut element = self
9377 .render_edit_prediction_line_popover(
9378 "Jump to Edit",
9379 Some(IconName::ArrowDown),
9380 window,
9381 cx,
9382 )
9383 .into_any();
9384
9385 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9386 let offset = point(
9387 (text_bounds.size.width - size.width) / 2.,
9388 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9389 );
9390
9391 let origin = text_bounds.origin + offset;
9392 element.prepaint_at(origin, window, cx);
9393 Some((element, origin))
9394 } else {
9395 self.render_edit_prediction_end_of_line_popover(
9396 "Jump to Edit",
9397 editor_snapshot,
9398 visible_row_range,
9399 target_display_point,
9400 line_height,
9401 scroll_pixel_position,
9402 content_origin,
9403 editor_width,
9404 window,
9405 cx,
9406 )
9407 }
9408 }
9409
9410 fn render_edit_prediction_end_of_line_popover(
9411 self: &mut Editor,
9412 label: &'static str,
9413 editor_snapshot: &EditorSnapshot,
9414 visible_row_range: Range<DisplayRow>,
9415 target_display_point: DisplayPoint,
9416 line_height: Pixels,
9417 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9418 content_origin: gpui::Point<Pixels>,
9419 editor_width: Pixels,
9420 window: &mut Window,
9421 cx: &mut App,
9422 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9423 let target_line_end = DisplayPoint::new(
9424 target_display_point.row(),
9425 editor_snapshot.line_len(target_display_point.row()),
9426 );
9427
9428 let mut element = self
9429 .render_edit_prediction_line_popover(label, None, window, cx)
9430 .into_any();
9431
9432 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9433
9434 let line_origin =
9435 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9436
9437 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9438 let mut origin = start_point
9439 + line_origin
9440 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9441 origin.x = origin.x.max(content_origin.x);
9442
9443 let max_x = content_origin.x + editor_width - size.width;
9444
9445 if origin.x > max_x {
9446 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9447
9448 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9449 origin.y += offset;
9450 IconName::ArrowUp
9451 } else {
9452 origin.y -= offset;
9453 IconName::ArrowDown
9454 };
9455
9456 element = self
9457 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9458 .into_any();
9459
9460 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9461
9462 origin.x = content_origin.x + editor_width - size.width - px(2.);
9463 }
9464
9465 element.prepaint_at(origin, window, cx);
9466 Some((element, origin))
9467 }
9468
9469 fn render_edit_prediction_diff_popover(
9470 self: &Editor,
9471 text_bounds: &Bounds<Pixels>,
9472 content_origin: gpui::Point<Pixels>,
9473 right_margin: Pixels,
9474 editor_snapshot: &EditorSnapshot,
9475 visible_row_range: Range<DisplayRow>,
9476 line_layouts: &[LineWithInvisibles],
9477 line_height: Pixels,
9478 scroll_position: gpui::Point<ScrollOffset>,
9479 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9480 newest_selection_head: Option<DisplayPoint>,
9481 editor_width: Pixels,
9482 style: &EditorStyle,
9483 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9484 edit_preview: &Option<language::EditPreview>,
9485 snapshot: &language::BufferSnapshot,
9486 window: &mut Window,
9487 cx: &mut App,
9488 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9489 let edit_start = edits
9490 .first()
9491 .unwrap()
9492 .0
9493 .start
9494 .to_display_point(editor_snapshot);
9495 let edit_end = edits
9496 .last()
9497 .unwrap()
9498 .0
9499 .end
9500 .to_display_point(editor_snapshot);
9501
9502 let is_visible = visible_row_range.contains(&edit_start.row())
9503 || visible_row_range.contains(&edit_end.row());
9504 if !is_visible {
9505 return None;
9506 }
9507
9508 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9509 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9510 } else {
9511 // Fallback for providers without edit_preview
9512 crate::edit_prediction_fallback_text(edits, cx)
9513 };
9514
9515 let styled_text = highlighted_edits.to_styled_text(&style.text);
9516 let line_count = highlighted_edits.text.lines().count();
9517
9518 const BORDER_WIDTH: Pixels = px(1.);
9519
9520 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9521 let has_keybind = keybind.is_some();
9522
9523 let mut element = h_flex()
9524 .items_start()
9525 .child(
9526 h_flex()
9527 .bg(cx.theme().colors().editor_background)
9528 .border(BORDER_WIDTH)
9529 .shadow_xs()
9530 .border_color(cx.theme().colors().border)
9531 .rounded_l_lg()
9532 .when(line_count > 1, |el| el.rounded_br_lg())
9533 .pr_1()
9534 .child(styled_text),
9535 )
9536 .child(
9537 h_flex()
9538 .h(line_height + BORDER_WIDTH * 2.)
9539 .px_1p5()
9540 .gap_1()
9541 // Workaround: For some reason, there's a gap if we don't do this
9542 .ml(-BORDER_WIDTH)
9543 .shadow(vec![gpui::BoxShadow {
9544 color: gpui::black().opacity(0.05),
9545 offset: point(px(1.), px(1.)),
9546 blur_radius: px(2.),
9547 spread_radius: px(0.),
9548 }])
9549 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9550 .border(BORDER_WIDTH)
9551 .border_color(cx.theme().colors().border)
9552 .rounded_r_lg()
9553 .id("edit_prediction_diff_popover_keybind")
9554 .when(!has_keybind, |el| {
9555 let status_colors = cx.theme().status();
9556
9557 el.bg(status_colors.error_background)
9558 .border_color(status_colors.error.opacity(0.6))
9559 .child(Icon::new(IconName::Info).color(Color::Error))
9560 .cursor_default()
9561 .hoverable_tooltip(move |_window, cx| {
9562 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9563 })
9564 })
9565 .children(keybind),
9566 )
9567 .into_any();
9568
9569 let longest_row =
9570 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9571 let longest_line_width = if visible_row_range.contains(&longest_row) {
9572 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9573 } else {
9574 layout_line(
9575 longest_row,
9576 editor_snapshot,
9577 style,
9578 editor_width,
9579 |_| false,
9580 window,
9581 cx,
9582 )
9583 .width
9584 };
9585
9586 let viewport_bounds =
9587 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9588 right: -right_margin,
9589 ..Default::default()
9590 });
9591
9592 let x_after_longest = Pixels::from(
9593 ScrollPixelOffset::from(
9594 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9595 ) - scroll_pixel_position.x,
9596 );
9597
9598 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9599
9600 // Fully visible if it can be displayed within the window (allow overlapping other
9601 // panes). However, this is only allowed if the popover starts within text_bounds.
9602 let can_position_to_the_right = x_after_longest < text_bounds.right()
9603 && x_after_longest + element_bounds.width < viewport_bounds.right();
9604
9605 let mut origin = if can_position_to_the_right {
9606 point(
9607 x_after_longest,
9608 text_bounds.origin.y
9609 + Pixels::from(
9610 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9611 - scroll_pixel_position.y,
9612 ),
9613 )
9614 } else {
9615 let cursor_row = newest_selection_head.map(|head| head.row());
9616 let above_edit = edit_start
9617 .row()
9618 .0
9619 .checked_sub(line_count as u32)
9620 .map(DisplayRow);
9621 let below_edit = Some(edit_end.row() + 1);
9622 let above_cursor =
9623 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9624 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9625
9626 // Place the edit popover adjacent to the edit if there is a location
9627 // available that is onscreen and does not obscure the cursor. Otherwise,
9628 // place it adjacent to the cursor.
9629 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9630 .into_iter()
9631 .flatten()
9632 .find(|&start_row| {
9633 let end_row = start_row + line_count as u32;
9634 visible_row_range.contains(&start_row)
9635 && visible_row_range.contains(&end_row)
9636 && cursor_row
9637 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9638 })?;
9639
9640 content_origin
9641 + point(
9642 Pixels::from(-scroll_pixel_position.x),
9643 Pixels::from(
9644 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9645 ),
9646 )
9647 };
9648
9649 origin.x -= BORDER_WIDTH;
9650
9651 window.defer_draw(element, origin, 1);
9652
9653 // Do not return an element, since it will already be drawn due to defer_draw.
9654 None
9655 }
9656
9657 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9658 px(30.)
9659 }
9660
9661 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9662 if self.read_only(cx) {
9663 cx.theme().players().read_only()
9664 } else {
9665 self.style.as_ref().unwrap().local_player
9666 }
9667 }
9668
9669 fn render_edit_prediction_accept_keybind(
9670 &self,
9671 window: &mut Window,
9672 cx: &mut App,
9673 ) -> Option<AnyElement> {
9674 let accept_binding =
9675 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9676 let accept_keystroke = accept_binding.keystroke()?;
9677
9678 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9679
9680 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9681 Color::Accent
9682 } else {
9683 Color::Muted
9684 };
9685
9686 h_flex()
9687 .px_0p5()
9688 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9689 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9690 .text_size(TextSize::XSmall.rems(cx))
9691 .child(h_flex().children(ui::render_modifiers(
9692 accept_keystroke.modifiers(),
9693 PlatformStyle::platform(),
9694 Some(modifiers_color),
9695 Some(IconSize::XSmall.rems().into()),
9696 true,
9697 )))
9698 .when(is_platform_style_mac, |parent| {
9699 parent.child(accept_keystroke.key().to_string())
9700 })
9701 .when(!is_platform_style_mac, |parent| {
9702 parent.child(
9703 Key::new(
9704 util::capitalize(accept_keystroke.key()),
9705 Some(Color::Default),
9706 )
9707 .size(Some(IconSize::XSmall.rems().into())),
9708 )
9709 })
9710 .into_any()
9711 .into()
9712 }
9713
9714 fn render_edit_prediction_line_popover(
9715 &self,
9716 label: impl Into<SharedString>,
9717 icon: Option<IconName>,
9718 window: &mut Window,
9719 cx: &mut App,
9720 ) -> Stateful<Div> {
9721 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9722
9723 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9724 let has_keybind = keybind.is_some();
9725
9726 h_flex()
9727 .id("ep-line-popover")
9728 .py_0p5()
9729 .pl_1()
9730 .pr(padding_right)
9731 .gap_1()
9732 .rounded_md()
9733 .border_1()
9734 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9735 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9736 .shadow_xs()
9737 .when(!has_keybind, |el| {
9738 let status_colors = cx.theme().status();
9739
9740 el.bg(status_colors.error_background)
9741 .border_color(status_colors.error.opacity(0.6))
9742 .pl_2()
9743 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9744 .cursor_default()
9745 .hoverable_tooltip(move |_window, cx| {
9746 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9747 })
9748 })
9749 .children(keybind)
9750 .child(
9751 Label::new(label)
9752 .size(LabelSize::Small)
9753 .when(!has_keybind, |el| {
9754 el.color(cx.theme().status().error.into()).strikethrough()
9755 }),
9756 )
9757 .when(!has_keybind, |el| {
9758 el.child(
9759 h_flex().ml_1().child(
9760 Icon::new(IconName::Info)
9761 .size(IconSize::Small)
9762 .color(cx.theme().status().error.into()),
9763 ),
9764 )
9765 })
9766 .when_some(icon, |element, icon| {
9767 element.child(
9768 div()
9769 .mt(px(1.5))
9770 .child(Icon::new(icon).size(IconSize::Small)),
9771 )
9772 })
9773 }
9774
9775 fn render_edit_prediction_jump_outside_popover(
9776 &self,
9777 snapshot: &BufferSnapshot,
9778 window: &mut Window,
9779 cx: &mut App,
9780 ) -> Stateful<Div> {
9781 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9782 let has_keybind = keybind.is_some();
9783
9784 let file_name = snapshot
9785 .file()
9786 .map(|file| SharedString::new(file.file_name(cx)))
9787 .unwrap_or(SharedString::new_static("untitled"));
9788
9789 h_flex()
9790 .id("ep-jump-outside-popover")
9791 .py_1()
9792 .px_2()
9793 .gap_1()
9794 .rounded_md()
9795 .border_1()
9796 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9797 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9798 .shadow_xs()
9799 .when(!has_keybind, |el| {
9800 let status_colors = cx.theme().status();
9801
9802 el.bg(status_colors.error_background)
9803 .border_color(status_colors.error.opacity(0.6))
9804 .pl_2()
9805 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9806 .cursor_default()
9807 .hoverable_tooltip(move |_window, cx| {
9808 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9809 })
9810 })
9811 .children(keybind)
9812 .child(
9813 Label::new(file_name)
9814 .size(LabelSize::Small)
9815 .buffer_font(cx)
9816 .when(!has_keybind, |el| {
9817 el.color(cx.theme().status().error.into()).strikethrough()
9818 }),
9819 )
9820 .when(!has_keybind, |el| {
9821 el.child(
9822 h_flex().ml_1().child(
9823 Icon::new(IconName::Info)
9824 .size(IconSize::Small)
9825 .color(cx.theme().status().error.into()),
9826 ),
9827 )
9828 })
9829 .child(
9830 div()
9831 .mt(px(1.5))
9832 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9833 )
9834 }
9835
9836 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9837 let accent_color = cx.theme().colors().text_accent;
9838 let editor_bg_color = cx.theme().colors().editor_background;
9839 editor_bg_color.blend(accent_color.opacity(0.1))
9840 }
9841
9842 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9843 let accent_color = cx.theme().colors().text_accent;
9844 let editor_bg_color = cx.theme().colors().editor_background;
9845 editor_bg_color.blend(accent_color.opacity(0.6))
9846 }
9847 fn get_prediction_provider_icon_name(
9848 provider: &Option<RegisteredEditPredictionDelegate>,
9849 ) -> IconName {
9850 match provider {
9851 Some(provider) => match provider.provider.name() {
9852 "copilot" => IconName::Copilot,
9853 "supermaven" => IconName::Supermaven,
9854 _ => IconName::ZedPredict,
9855 },
9856 None => IconName::ZedPredict,
9857 }
9858 }
9859
9860 fn render_edit_prediction_cursor_popover(
9861 &self,
9862 min_width: Pixels,
9863 max_width: Pixels,
9864 cursor_point: Point,
9865 style: &EditorStyle,
9866 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9867 _window: &Window,
9868 cx: &mut Context<Editor>,
9869 ) -> Option<AnyElement> {
9870 let provider = self.edit_prediction_provider.as_ref()?;
9871 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9872
9873 let is_refreshing = provider.provider.is_refreshing(cx);
9874
9875 fn pending_completion_container(icon: IconName) -> Div {
9876 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9877 }
9878
9879 let completion = match &self.active_edit_prediction {
9880 Some(prediction) => {
9881 if !self.has_visible_completions_menu() {
9882 const RADIUS: Pixels = px(6.);
9883 const BORDER_WIDTH: Pixels = px(1.);
9884
9885 return Some(
9886 h_flex()
9887 .elevation_2(cx)
9888 .border(BORDER_WIDTH)
9889 .border_color(cx.theme().colors().border)
9890 .when(accept_keystroke.is_none(), |el| {
9891 el.border_color(cx.theme().status().error)
9892 })
9893 .rounded(RADIUS)
9894 .rounded_tl(px(0.))
9895 .overflow_hidden()
9896 .child(div().px_1p5().child(match &prediction.completion {
9897 EditPrediction::MoveWithin { target, snapshot } => {
9898 use text::ToPoint as _;
9899 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9900 {
9901 Icon::new(IconName::ZedPredictDown)
9902 } else {
9903 Icon::new(IconName::ZedPredictUp)
9904 }
9905 }
9906 EditPrediction::MoveOutside { .. } => {
9907 // TODO [zeta2] custom icon for external jump?
9908 Icon::new(provider_icon)
9909 }
9910 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9911 }))
9912 .child(
9913 h_flex()
9914 .gap_1()
9915 .py_1()
9916 .px_2()
9917 .rounded_r(RADIUS - BORDER_WIDTH)
9918 .border_l_1()
9919 .border_color(cx.theme().colors().border)
9920 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9921 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9922 el.child(
9923 Label::new("Hold")
9924 .size(LabelSize::Small)
9925 .when(accept_keystroke.is_none(), |el| {
9926 el.strikethrough()
9927 })
9928 .line_height_style(LineHeightStyle::UiLabel),
9929 )
9930 })
9931 .id("edit_prediction_cursor_popover_keybind")
9932 .when(accept_keystroke.is_none(), |el| {
9933 let status_colors = cx.theme().status();
9934
9935 el.bg(status_colors.error_background)
9936 .border_color(status_colors.error.opacity(0.6))
9937 .child(Icon::new(IconName::Info).color(Color::Error))
9938 .cursor_default()
9939 .hoverable_tooltip(move |_window, cx| {
9940 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9941 .into()
9942 })
9943 })
9944 .when_some(
9945 accept_keystroke.as_ref(),
9946 |el, accept_keystroke| {
9947 el.child(h_flex().children(ui::render_modifiers(
9948 accept_keystroke.modifiers(),
9949 PlatformStyle::platform(),
9950 Some(Color::Default),
9951 Some(IconSize::XSmall.rems().into()),
9952 false,
9953 )))
9954 },
9955 ),
9956 )
9957 .into_any(),
9958 );
9959 }
9960
9961 self.render_edit_prediction_cursor_popover_preview(
9962 prediction,
9963 cursor_point,
9964 style,
9965 cx,
9966 )?
9967 }
9968
9969 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9970 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9971 stale_completion,
9972 cursor_point,
9973 style,
9974 cx,
9975 )?,
9976
9977 None => pending_completion_container(provider_icon)
9978 .child(Label::new("...").size(LabelSize::Small)),
9979 },
9980
9981 None => pending_completion_container(provider_icon)
9982 .child(Label::new("...").size(LabelSize::Small)),
9983 };
9984
9985 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9986 completion
9987 .with_animation(
9988 "loading-completion",
9989 Animation::new(Duration::from_secs(2))
9990 .repeat()
9991 .with_easing(pulsating_between(0.4, 0.8)),
9992 |label, delta| label.opacity(delta),
9993 )
9994 .into_any_element()
9995 } else {
9996 completion.into_any_element()
9997 };
9998
9999 let has_completion = self.active_edit_prediction.is_some();
10000
10001 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10002 Some(
10003 h_flex()
10004 .min_w(min_width)
10005 .max_w(max_width)
10006 .flex_1()
10007 .elevation_2(cx)
10008 .border_color(cx.theme().colors().border)
10009 .child(
10010 div()
10011 .flex_1()
10012 .py_1()
10013 .px_2()
10014 .overflow_hidden()
10015 .child(completion),
10016 )
10017 .when_some(accept_keystroke, |el, accept_keystroke| {
10018 if !accept_keystroke.modifiers().modified() {
10019 return el;
10020 }
10021
10022 el.child(
10023 h_flex()
10024 .h_full()
10025 .border_l_1()
10026 .rounded_r_lg()
10027 .border_color(cx.theme().colors().border)
10028 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10029 .gap_1()
10030 .py_1()
10031 .px_2()
10032 .child(
10033 h_flex()
10034 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10035 .when(is_platform_style_mac, |parent| parent.gap_1())
10036 .child(h_flex().children(ui::render_modifiers(
10037 accept_keystroke.modifiers(),
10038 PlatformStyle::platform(),
10039 Some(if !has_completion {
10040 Color::Muted
10041 } else {
10042 Color::Default
10043 }),
10044 None,
10045 false,
10046 ))),
10047 )
10048 .child(Label::new("Preview").into_any_element())
10049 .opacity(if has_completion { 1.0 } else { 0.4 }),
10050 )
10051 })
10052 .into_any(),
10053 )
10054 }
10055
10056 fn render_edit_prediction_cursor_popover_preview(
10057 &self,
10058 completion: &EditPredictionState,
10059 cursor_point: Point,
10060 style: &EditorStyle,
10061 cx: &mut Context<Editor>,
10062 ) -> Option<Div> {
10063 use text::ToPoint as _;
10064
10065 fn render_relative_row_jump(
10066 prefix: impl Into<String>,
10067 current_row: u32,
10068 target_row: u32,
10069 ) -> Div {
10070 let (row_diff, arrow) = if target_row < current_row {
10071 (current_row - target_row, IconName::ArrowUp)
10072 } else {
10073 (target_row - current_row, IconName::ArrowDown)
10074 };
10075
10076 h_flex()
10077 .child(
10078 Label::new(format!("{}{}", prefix.into(), row_diff))
10079 .color(Color::Muted)
10080 .size(LabelSize::Small),
10081 )
10082 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10083 }
10084
10085 let supports_jump = self
10086 .edit_prediction_provider
10087 .as_ref()
10088 .map(|provider| provider.provider.supports_jump_to_edit())
10089 .unwrap_or(true);
10090
10091 match &completion.completion {
10092 EditPrediction::MoveWithin {
10093 target, snapshot, ..
10094 } => {
10095 if !supports_jump {
10096 return None;
10097 }
10098
10099 Some(
10100 h_flex()
10101 .px_2()
10102 .gap_2()
10103 .flex_1()
10104 .child(
10105 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10106 Icon::new(IconName::ZedPredictDown)
10107 } else {
10108 Icon::new(IconName::ZedPredictUp)
10109 },
10110 )
10111 .child(Label::new("Jump to Edit")),
10112 )
10113 }
10114 EditPrediction::MoveOutside { snapshot, .. } => {
10115 let file_name = snapshot
10116 .file()
10117 .map(|file| file.file_name(cx))
10118 .unwrap_or("untitled");
10119 Some(
10120 h_flex()
10121 .px_2()
10122 .gap_2()
10123 .flex_1()
10124 .child(Icon::new(IconName::ZedPredict))
10125 .child(Label::new(format!("Jump to {file_name}"))),
10126 )
10127 }
10128 EditPrediction::Edit {
10129 edits,
10130 edit_preview,
10131 snapshot,
10132 display_mode: _,
10133 } => {
10134 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10135
10136 let (highlighted_edits, has_more_lines) =
10137 if let Some(edit_preview) = edit_preview.as_ref() {
10138 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10139 .first_line_preview()
10140 } else {
10141 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10142 };
10143
10144 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10145 .with_default_highlights(&style.text, highlighted_edits.highlights);
10146
10147 let preview = h_flex()
10148 .gap_1()
10149 .min_w_16()
10150 .child(styled_text)
10151 .when(has_more_lines, |parent| parent.child("…"));
10152
10153 let left = if supports_jump && first_edit_row != cursor_point.row {
10154 render_relative_row_jump("", cursor_point.row, first_edit_row)
10155 .into_any_element()
10156 } else {
10157 let icon_name =
10158 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
10159 Icon::new(icon_name).into_any_element()
10160 };
10161
10162 Some(
10163 h_flex()
10164 .h_full()
10165 .flex_1()
10166 .gap_2()
10167 .pr_1()
10168 .overflow_x_hidden()
10169 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10170 .child(left)
10171 .child(preview),
10172 )
10173 }
10174 }
10175 }
10176
10177 pub fn render_context_menu(
10178 &mut self,
10179 max_height_in_lines: u32,
10180 window: &mut Window,
10181 cx: &mut Context<Editor>,
10182 ) -> Option<AnyElement> {
10183 let menu = self.context_menu.borrow();
10184 let menu = menu.as_ref()?;
10185 if !menu.visible() {
10186 return None;
10187 };
10188 self.style
10189 .as_ref()
10190 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10191 }
10192
10193 fn render_context_menu_aside(
10194 &mut self,
10195 max_size: Size<Pixels>,
10196 window: &mut Window,
10197 cx: &mut Context<Editor>,
10198 ) -> Option<AnyElement> {
10199 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10200 if menu.visible() {
10201 menu.render_aside(max_size, window, cx)
10202 } else {
10203 None
10204 }
10205 })
10206 }
10207
10208 fn hide_context_menu(
10209 &mut self,
10210 window: &mut Window,
10211 cx: &mut Context<Self>,
10212 ) -> Option<CodeContextMenu> {
10213 cx.notify();
10214 self.completion_tasks.clear();
10215 let context_menu = self.context_menu.borrow_mut().take();
10216 self.stale_edit_prediction_in_menu.take();
10217 self.update_visible_edit_prediction(window, cx);
10218 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10219 && let Some(completion_provider) = &self.completion_provider
10220 {
10221 completion_provider.selection_changed(None, window, cx);
10222 }
10223 context_menu
10224 }
10225
10226 fn show_snippet_choices(
10227 &mut self,
10228 choices: &Vec<String>,
10229 selection: Range<Anchor>,
10230 cx: &mut Context<Self>,
10231 ) {
10232 let Some((_, buffer, _)) = self
10233 .buffer()
10234 .read(cx)
10235 .excerpt_containing(selection.start, cx)
10236 else {
10237 return;
10238 };
10239 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10240 else {
10241 return;
10242 };
10243 if buffer != end_buffer {
10244 log::error!("expected anchor range to have matching buffer IDs");
10245 return;
10246 }
10247
10248 let id = post_inc(&mut self.next_completion_id);
10249 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10250 let mut context_menu = self.context_menu.borrow_mut();
10251 let old_menu = context_menu.take();
10252 *context_menu = Some(CodeContextMenu::Completions(
10253 CompletionsMenu::new_snippet_choices(
10254 id,
10255 true,
10256 choices,
10257 selection,
10258 buffer,
10259 old_menu.map(|menu| menu.primary_scroll_handle()),
10260 snippet_sort_order,
10261 ),
10262 ));
10263 }
10264
10265 pub fn insert_snippet(
10266 &mut self,
10267 insertion_ranges: &[Range<MultiBufferOffset>],
10268 snippet: Snippet,
10269 window: &mut Window,
10270 cx: &mut Context<Self>,
10271 ) -> Result<()> {
10272 struct Tabstop<T> {
10273 is_end_tabstop: bool,
10274 ranges: Vec<Range<T>>,
10275 choices: Option<Vec<String>>,
10276 }
10277
10278 let tabstops = self.buffer.update(cx, |buffer, cx| {
10279 let snippet_text: Arc<str> = snippet.text.clone().into();
10280 let edits = insertion_ranges
10281 .iter()
10282 .cloned()
10283 .map(|range| (range, snippet_text.clone()));
10284 let autoindent_mode = AutoindentMode::Block {
10285 original_indent_columns: Vec::new(),
10286 };
10287 buffer.edit(edits, Some(autoindent_mode), cx);
10288
10289 let snapshot = &*buffer.read(cx);
10290 let snippet = &snippet;
10291 snippet
10292 .tabstops
10293 .iter()
10294 .map(|tabstop| {
10295 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10296 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10297 });
10298 let mut tabstop_ranges = tabstop
10299 .ranges
10300 .iter()
10301 .flat_map(|tabstop_range| {
10302 let mut delta = 0_isize;
10303 insertion_ranges.iter().map(move |insertion_range| {
10304 let insertion_start = insertion_range.start + delta;
10305 delta += snippet.text.len() as isize
10306 - (insertion_range.end - insertion_range.start) as isize;
10307
10308 let start =
10309 (insertion_start + tabstop_range.start).min(snapshot.len());
10310 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10311 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10312 })
10313 })
10314 .collect::<Vec<_>>();
10315 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10316
10317 Tabstop {
10318 is_end_tabstop,
10319 ranges: tabstop_ranges,
10320 choices: tabstop.choices.clone(),
10321 }
10322 })
10323 .collect::<Vec<_>>()
10324 });
10325 if let Some(tabstop) = tabstops.first() {
10326 self.change_selections(Default::default(), window, cx, |s| {
10327 // Reverse order so that the first range is the newest created selection.
10328 // Completions will use it and autoscroll will prioritize it.
10329 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10330 });
10331
10332 if let Some(choices) = &tabstop.choices
10333 && let Some(selection) = tabstop.ranges.first()
10334 {
10335 self.show_snippet_choices(choices, selection.clone(), cx)
10336 }
10337
10338 // If we're already at the last tabstop and it's at the end of the snippet,
10339 // we're done, we don't need to keep the state around.
10340 if !tabstop.is_end_tabstop {
10341 let choices = tabstops
10342 .iter()
10343 .map(|tabstop| tabstop.choices.clone())
10344 .collect();
10345
10346 let ranges = tabstops
10347 .into_iter()
10348 .map(|tabstop| tabstop.ranges)
10349 .collect::<Vec<_>>();
10350
10351 self.snippet_stack.push(SnippetState {
10352 active_index: 0,
10353 ranges,
10354 choices,
10355 });
10356 }
10357
10358 // Check whether the just-entered snippet ends with an auto-closable bracket.
10359 if self.autoclose_regions.is_empty() {
10360 let snapshot = self.buffer.read(cx).snapshot(cx);
10361 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10362 let selection_head = selection.head();
10363 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10364 continue;
10365 };
10366
10367 let mut bracket_pair = None;
10368 let max_lookup_length = scope
10369 .brackets()
10370 .map(|(pair, _)| {
10371 pair.start
10372 .as_str()
10373 .chars()
10374 .count()
10375 .max(pair.end.as_str().chars().count())
10376 })
10377 .max();
10378 if let Some(max_lookup_length) = max_lookup_length {
10379 let next_text = snapshot
10380 .chars_at(selection_head)
10381 .take(max_lookup_length)
10382 .collect::<String>();
10383 let prev_text = snapshot
10384 .reversed_chars_at(selection_head)
10385 .take(max_lookup_length)
10386 .collect::<String>();
10387
10388 for (pair, enabled) in scope.brackets() {
10389 if enabled
10390 && pair.close
10391 && prev_text.starts_with(pair.start.as_str())
10392 && next_text.starts_with(pair.end.as_str())
10393 {
10394 bracket_pair = Some(pair.clone());
10395 break;
10396 }
10397 }
10398 }
10399
10400 if let Some(pair) = bracket_pair {
10401 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10402 let autoclose_enabled =
10403 self.use_autoclose && snapshot_settings.use_autoclose;
10404 if autoclose_enabled {
10405 let start = snapshot.anchor_after(selection_head);
10406 let end = snapshot.anchor_after(selection_head);
10407 self.autoclose_regions.push(AutocloseRegion {
10408 selection_id: selection.id,
10409 range: start..end,
10410 pair,
10411 });
10412 }
10413 }
10414 }
10415 }
10416 }
10417 Ok(())
10418 }
10419
10420 pub fn move_to_next_snippet_tabstop(
10421 &mut self,
10422 window: &mut Window,
10423 cx: &mut Context<Self>,
10424 ) -> bool {
10425 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10426 }
10427
10428 pub fn move_to_prev_snippet_tabstop(
10429 &mut self,
10430 window: &mut Window,
10431 cx: &mut Context<Self>,
10432 ) -> bool {
10433 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10434 }
10435
10436 pub fn move_to_snippet_tabstop(
10437 &mut self,
10438 bias: Bias,
10439 window: &mut Window,
10440 cx: &mut Context<Self>,
10441 ) -> bool {
10442 if let Some(mut snippet) = self.snippet_stack.pop() {
10443 match bias {
10444 Bias::Left => {
10445 if snippet.active_index > 0 {
10446 snippet.active_index -= 1;
10447 } else {
10448 self.snippet_stack.push(snippet);
10449 return false;
10450 }
10451 }
10452 Bias::Right => {
10453 if snippet.active_index + 1 < snippet.ranges.len() {
10454 snippet.active_index += 1;
10455 } else {
10456 self.snippet_stack.push(snippet);
10457 return false;
10458 }
10459 }
10460 }
10461 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10462 self.change_selections(Default::default(), window, cx, |s| {
10463 // Reverse order so that the first range is the newest created selection.
10464 // Completions will use it and autoscroll will prioritize it.
10465 s.select_ranges(current_ranges.iter().rev().cloned())
10466 });
10467
10468 if let Some(choices) = &snippet.choices[snippet.active_index]
10469 && let Some(selection) = current_ranges.first()
10470 {
10471 self.show_snippet_choices(choices, selection.clone(), cx);
10472 }
10473
10474 // If snippet state is not at the last tabstop, push it back on the stack
10475 if snippet.active_index + 1 < snippet.ranges.len() {
10476 self.snippet_stack.push(snippet);
10477 }
10478 return true;
10479 }
10480 }
10481
10482 false
10483 }
10484
10485 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10486 self.transact(window, cx, |this, window, cx| {
10487 this.select_all(&SelectAll, window, cx);
10488 this.insert("", window, cx);
10489 });
10490 }
10491
10492 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10493 if self.read_only(cx) {
10494 return;
10495 }
10496 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10497 self.transact(window, cx, |this, window, cx| {
10498 this.select_autoclose_pair(window, cx);
10499
10500 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10501
10502 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10503 if !this.linked_edit_ranges.is_empty() {
10504 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10505 let snapshot = this.buffer.read(cx).snapshot(cx);
10506
10507 for selection in selections.iter() {
10508 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10509 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10510 if selection_start.buffer_id != selection_end.buffer_id {
10511 continue;
10512 }
10513 if let Some(ranges) =
10514 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10515 {
10516 for (buffer, entries) in ranges {
10517 linked_ranges.entry(buffer).or_default().extend(entries);
10518 }
10519 }
10520 }
10521 }
10522
10523 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10524 for selection in &mut selections {
10525 if selection.is_empty() {
10526 let old_head = selection.head();
10527 let mut new_head =
10528 movement::left(&display_map, old_head.to_display_point(&display_map))
10529 .to_point(&display_map);
10530 if let Some((buffer, line_buffer_range)) = display_map
10531 .buffer_snapshot()
10532 .buffer_line_for_row(MultiBufferRow(old_head.row))
10533 {
10534 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10535 let indent_len = match indent_size.kind {
10536 IndentKind::Space => {
10537 buffer.settings_at(line_buffer_range.start, cx).tab_size
10538 }
10539 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10540 };
10541 if old_head.column <= indent_size.len && old_head.column > 0 {
10542 let indent_len = indent_len.get();
10543 new_head = cmp::min(
10544 new_head,
10545 MultiBufferPoint::new(
10546 old_head.row,
10547 ((old_head.column - 1) / indent_len) * indent_len,
10548 ),
10549 );
10550 }
10551 }
10552
10553 selection.set_head(new_head, SelectionGoal::None);
10554 }
10555 }
10556
10557 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10558 this.insert("", window, cx);
10559 let empty_str: Arc<str> = Arc::from("");
10560 for (buffer, edits) in linked_ranges {
10561 let snapshot = buffer.read(cx).snapshot();
10562 use text::ToPoint as TP;
10563
10564 let edits = edits
10565 .into_iter()
10566 .map(|range| {
10567 let end_point = TP::to_point(&range.end, &snapshot);
10568 let mut start_point = TP::to_point(&range.start, &snapshot);
10569
10570 if end_point == start_point {
10571 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10572 .saturating_sub(1);
10573 start_point =
10574 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10575 };
10576
10577 (start_point..end_point, empty_str.clone())
10578 })
10579 .sorted_by_key(|(range, _)| range.start)
10580 .collect::<Vec<_>>();
10581 buffer.update(cx, |this, cx| {
10582 this.edit(edits, None, cx);
10583 })
10584 }
10585 this.refresh_edit_prediction(true, false, window, cx);
10586 refresh_linked_ranges(this, window, cx);
10587 });
10588 }
10589
10590 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10591 if self.read_only(cx) {
10592 return;
10593 }
10594 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10595 self.transact(window, cx, |this, window, cx| {
10596 this.change_selections(Default::default(), window, cx, |s| {
10597 s.move_with(|map, selection| {
10598 if selection.is_empty() {
10599 let cursor = movement::right(map, selection.head());
10600 selection.end = cursor;
10601 selection.reversed = true;
10602 selection.goal = SelectionGoal::None;
10603 }
10604 })
10605 });
10606 this.insert("", window, cx);
10607 this.refresh_edit_prediction(true, false, window, cx);
10608 });
10609 }
10610
10611 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10612 if self.mode.is_single_line() {
10613 cx.propagate();
10614 return;
10615 }
10616
10617 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10618 if self.move_to_prev_snippet_tabstop(window, cx) {
10619 return;
10620 }
10621 self.outdent(&Outdent, window, cx);
10622 }
10623
10624 pub fn next_snippet_tabstop(
10625 &mut self,
10626 _: &NextSnippetTabstop,
10627 window: &mut Window,
10628 cx: &mut Context<Self>,
10629 ) {
10630 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10631 cx.propagate();
10632 return;
10633 }
10634
10635 if self.move_to_next_snippet_tabstop(window, cx) {
10636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10637 return;
10638 }
10639 cx.propagate();
10640 }
10641
10642 pub fn previous_snippet_tabstop(
10643 &mut self,
10644 _: &PreviousSnippetTabstop,
10645 window: &mut Window,
10646 cx: &mut Context<Self>,
10647 ) {
10648 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10649 cx.propagate();
10650 return;
10651 }
10652
10653 if self.move_to_prev_snippet_tabstop(window, cx) {
10654 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10655 return;
10656 }
10657 cx.propagate();
10658 }
10659
10660 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10661 if self.mode.is_single_line() {
10662 cx.propagate();
10663 return;
10664 }
10665
10666 if self.move_to_next_snippet_tabstop(window, cx) {
10667 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10668 return;
10669 }
10670 if self.read_only(cx) {
10671 return;
10672 }
10673 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10674 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10675 let buffer = self.buffer.read(cx);
10676 let snapshot = buffer.snapshot(cx);
10677 let rows_iter = selections.iter().map(|s| s.head().row);
10678 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10679
10680 let has_some_cursor_in_whitespace = selections
10681 .iter()
10682 .filter(|selection| selection.is_empty())
10683 .any(|selection| {
10684 let cursor = selection.head();
10685 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10686 cursor.column < current_indent.len
10687 });
10688
10689 let mut edits = Vec::new();
10690 let mut prev_edited_row = 0;
10691 let mut row_delta = 0;
10692 for selection in &mut selections {
10693 if selection.start.row != prev_edited_row {
10694 row_delta = 0;
10695 }
10696 prev_edited_row = selection.end.row;
10697
10698 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10699 if selection.is_empty() {
10700 let cursor = selection.head();
10701 let settings = buffer.language_settings_at(cursor, cx);
10702 if settings.indent_list_on_tab {
10703 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10704 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10705 row_delta = Self::indent_selection(
10706 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10707 );
10708 continue;
10709 }
10710 }
10711 }
10712 }
10713
10714 // If the selection is non-empty, then increase the indentation of the selected lines.
10715 if !selection.is_empty() {
10716 row_delta =
10717 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10718 continue;
10719 }
10720
10721 let cursor = selection.head();
10722 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10723 if let Some(suggested_indent) =
10724 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10725 {
10726 // Don't do anything if already at suggested indent
10727 // and there is any other cursor which is not
10728 if has_some_cursor_in_whitespace
10729 && cursor.column == current_indent.len
10730 && current_indent.len == suggested_indent.len
10731 {
10732 continue;
10733 }
10734
10735 // Adjust line and move cursor to suggested indent
10736 // if cursor is not at suggested indent
10737 if cursor.column < suggested_indent.len
10738 && cursor.column <= current_indent.len
10739 && current_indent.len <= suggested_indent.len
10740 {
10741 selection.start = Point::new(cursor.row, suggested_indent.len);
10742 selection.end = selection.start;
10743 if row_delta == 0 {
10744 edits.extend(Buffer::edit_for_indent_size_adjustment(
10745 cursor.row,
10746 current_indent,
10747 suggested_indent,
10748 ));
10749 row_delta = suggested_indent.len - current_indent.len;
10750 }
10751 continue;
10752 }
10753
10754 // If current indent is more than suggested indent
10755 // only move cursor to current indent and skip indent
10756 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10757 selection.start = Point::new(cursor.row, current_indent.len);
10758 selection.end = selection.start;
10759 continue;
10760 }
10761 }
10762
10763 // Otherwise, insert a hard or soft tab.
10764 let settings = buffer.language_settings_at(cursor, cx);
10765 let tab_size = if settings.hard_tabs {
10766 IndentSize::tab()
10767 } else {
10768 let tab_size = settings.tab_size.get();
10769 let indent_remainder = snapshot
10770 .text_for_range(Point::new(cursor.row, 0)..cursor)
10771 .flat_map(str::chars)
10772 .fold(row_delta % tab_size, |counter: u32, c| {
10773 if c == '\t' {
10774 0
10775 } else {
10776 (counter + 1) % tab_size
10777 }
10778 });
10779
10780 let chars_to_next_tab_stop = tab_size - indent_remainder;
10781 IndentSize::spaces(chars_to_next_tab_stop)
10782 };
10783 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10784 selection.end = selection.start;
10785 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10786 row_delta += tab_size.len;
10787 }
10788
10789 self.transact(window, cx, |this, window, cx| {
10790 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10791 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10792 this.refresh_edit_prediction(true, false, window, cx);
10793 });
10794 }
10795
10796 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10797 if self.read_only(cx) {
10798 return;
10799 }
10800 if self.mode.is_single_line() {
10801 cx.propagate();
10802 return;
10803 }
10804
10805 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10806 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10807 let mut prev_edited_row = 0;
10808 let mut row_delta = 0;
10809 let mut edits = Vec::new();
10810 let buffer = self.buffer.read(cx);
10811 let snapshot = buffer.snapshot(cx);
10812 for selection in &mut selections {
10813 if selection.start.row != prev_edited_row {
10814 row_delta = 0;
10815 }
10816 prev_edited_row = selection.end.row;
10817
10818 row_delta =
10819 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10820 }
10821
10822 self.transact(window, cx, |this, window, cx| {
10823 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10824 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10825 });
10826 }
10827
10828 fn indent_selection(
10829 buffer: &MultiBuffer,
10830 snapshot: &MultiBufferSnapshot,
10831 selection: &mut Selection<Point>,
10832 edits: &mut Vec<(Range<Point>, String)>,
10833 delta_for_start_row: u32,
10834 cx: &App,
10835 ) -> u32 {
10836 let settings = buffer.language_settings_at(selection.start, cx);
10837 let tab_size = settings.tab_size.get();
10838 let indent_kind = if settings.hard_tabs {
10839 IndentKind::Tab
10840 } else {
10841 IndentKind::Space
10842 };
10843 let mut start_row = selection.start.row;
10844 let mut end_row = selection.end.row + 1;
10845
10846 // If a selection ends at the beginning of a line, don't indent
10847 // that last line.
10848 if selection.end.column == 0 && selection.end.row > selection.start.row {
10849 end_row -= 1;
10850 }
10851
10852 // Avoid re-indenting a row that has already been indented by a
10853 // previous selection, but still update this selection's column
10854 // to reflect that indentation.
10855 if delta_for_start_row > 0 {
10856 start_row += 1;
10857 selection.start.column += delta_for_start_row;
10858 if selection.end.row == selection.start.row {
10859 selection.end.column += delta_for_start_row;
10860 }
10861 }
10862
10863 let mut delta_for_end_row = 0;
10864 let has_multiple_rows = start_row + 1 != end_row;
10865 for row in start_row..end_row {
10866 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10867 let indent_delta = match (current_indent.kind, indent_kind) {
10868 (IndentKind::Space, IndentKind::Space) => {
10869 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10870 IndentSize::spaces(columns_to_next_tab_stop)
10871 }
10872 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10873 (_, IndentKind::Tab) => IndentSize::tab(),
10874 };
10875
10876 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10877 0
10878 } else {
10879 selection.start.column
10880 };
10881 let row_start = Point::new(row, start);
10882 edits.push((
10883 row_start..row_start,
10884 indent_delta.chars().collect::<String>(),
10885 ));
10886
10887 // Update this selection's endpoints to reflect the indentation.
10888 if row == selection.start.row {
10889 selection.start.column += indent_delta.len;
10890 }
10891 if row == selection.end.row {
10892 selection.end.column += indent_delta.len;
10893 delta_for_end_row = indent_delta.len;
10894 }
10895 }
10896
10897 if selection.start.row == selection.end.row {
10898 delta_for_start_row + delta_for_end_row
10899 } else {
10900 delta_for_end_row
10901 }
10902 }
10903
10904 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10905 if self.read_only(cx) {
10906 return;
10907 }
10908 if self.mode.is_single_line() {
10909 cx.propagate();
10910 return;
10911 }
10912
10913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10914 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10915 let selections = self.selections.all::<Point>(&display_map);
10916 let mut deletion_ranges = Vec::new();
10917 let mut last_outdent = None;
10918 {
10919 let buffer = self.buffer.read(cx);
10920 let snapshot = buffer.snapshot(cx);
10921 for selection in &selections {
10922 let settings = buffer.language_settings_at(selection.start, cx);
10923 let tab_size = settings.tab_size.get();
10924 let mut rows = selection.spanned_rows(false, &display_map);
10925
10926 // Avoid re-outdenting a row that has already been outdented by a
10927 // previous selection.
10928 if let Some(last_row) = last_outdent
10929 && last_row == rows.start
10930 {
10931 rows.start = rows.start.next_row();
10932 }
10933 let has_multiple_rows = rows.len() > 1;
10934 for row in rows.iter_rows() {
10935 let indent_size = snapshot.indent_size_for_line(row);
10936 if indent_size.len > 0 {
10937 let deletion_len = match indent_size.kind {
10938 IndentKind::Space => {
10939 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10940 if columns_to_prev_tab_stop == 0 {
10941 tab_size
10942 } else {
10943 columns_to_prev_tab_stop
10944 }
10945 }
10946 IndentKind::Tab => 1,
10947 };
10948 let start = if has_multiple_rows
10949 || deletion_len > selection.start.column
10950 || indent_size.len < selection.start.column
10951 {
10952 0
10953 } else {
10954 selection.start.column - deletion_len
10955 };
10956 deletion_ranges.push(
10957 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10958 );
10959 last_outdent = Some(row);
10960 }
10961 }
10962 }
10963 }
10964
10965 self.transact(window, cx, |this, window, cx| {
10966 this.buffer.update(cx, |buffer, cx| {
10967 let empty_str: Arc<str> = Arc::default();
10968 buffer.edit(
10969 deletion_ranges
10970 .into_iter()
10971 .map(|range| (range, empty_str.clone())),
10972 None,
10973 cx,
10974 );
10975 });
10976 let selections = this
10977 .selections
10978 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10979 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10980 });
10981 }
10982
10983 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10984 if self.read_only(cx) {
10985 return;
10986 }
10987 if self.mode.is_single_line() {
10988 cx.propagate();
10989 return;
10990 }
10991
10992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10993 let selections = self
10994 .selections
10995 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10996 .into_iter()
10997 .map(|s| s.range());
10998
10999 self.transact(window, cx, |this, window, cx| {
11000 this.buffer.update(cx, |buffer, cx| {
11001 buffer.autoindent_ranges(selections, cx);
11002 });
11003 let selections = this
11004 .selections
11005 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11006 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11007 });
11008 }
11009
11010 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11012 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11013 let selections = self.selections.all::<Point>(&display_map);
11014
11015 let mut new_cursors = Vec::new();
11016 let mut edit_ranges = Vec::new();
11017 let mut selections = selections.iter().peekable();
11018 while let Some(selection) = selections.next() {
11019 let mut rows = selection.spanned_rows(false, &display_map);
11020
11021 // Accumulate contiguous regions of rows that we want to delete.
11022 while let Some(next_selection) = selections.peek() {
11023 let next_rows = next_selection.spanned_rows(false, &display_map);
11024 if next_rows.start <= rows.end {
11025 rows.end = next_rows.end;
11026 selections.next().unwrap();
11027 } else {
11028 break;
11029 }
11030 }
11031
11032 let buffer = display_map.buffer_snapshot();
11033 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11034 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11035 // If there's a line after the range, delete the \n from the end of the row range
11036 (
11037 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11038 rows.end,
11039 )
11040 } else {
11041 // If there isn't a line after the range, delete the \n from the line before the
11042 // start of the row range
11043 edit_start = edit_start.saturating_sub_usize(1);
11044 (buffer.len(), rows.start.previous_row())
11045 };
11046
11047 let text_layout_details = self.text_layout_details(window);
11048 let x = display_map.x_for_display_point(
11049 selection.head().to_display_point(&display_map),
11050 &text_layout_details,
11051 );
11052 let row = Point::new(target_row.0, 0)
11053 .to_display_point(&display_map)
11054 .row();
11055 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11056
11057 new_cursors.push((
11058 selection.id,
11059 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11060 SelectionGoal::None,
11061 ));
11062 edit_ranges.push(edit_start..edit_end);
11063 }
11064
11065 self.transact(window, cx, |this, window, cx| {
11066 let buffer = this.buffer.update(cx, |buffer, cx| {
11067 let empty_str: Arc<str> = Arc::default();
11068 buffer.edit(
11069 edit_ranges
11070 .into_iter()
11071 .map(|range| (range, empty_str.clone())),
11072 None,
11073 cx,
11074 );
11075 buffer.snapshot(cx)
11076 });
11077 let new_selections = new_cursors
11078 .into_iter()
11079 .map(|(id, cursor, goal)| {
11080 let cursor = cursor.to_point(&buffer);
11081 Selection {
11082 id,
11083 start: cursor,
11084 end: cursor,
11085 reversed: false,
11086 goal,
11087 }
11088 })
11089 .collect();
11090
11091 this.change_selections(Default::default(), window, cx, |s| {
11092 s.select(new_selections);
11093 });
11094 });
11095 }
11096
11097 pub fn join_lines_impl(
11098 &mut self,
11099 insert_whitespace: bool,
11100 window: &mut Window,
11101 cx: &mut Context<Self>,
11102 ) {
11103 if self.read_only(cx) {
11104 return;
11105 }
11106 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11107 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11108 let start = MultiBufferRow(selection.start.row);
11109 // Treat single line selections as if they include the next line. Otherwise this action
11110 // would do nothing for single line selections individual cursors.
11111 let end = if selection.start.row == selection.end.row {
11112 MultiBufferRow(selection.start.row + 1)
11113 } else {
11114 MultiBufferRow(selection.end.row)
11115 };
11116
11117 if let Some(last_row_range) = row_ranges.last_mut()
11118 && start <= last_row_range.end
11119 {
11120 last_row_range.end = end;
11121 continue;
11122 }
11123 row_ranges.push(start..end);
11124 }
11125
11126 let snapshot = self.buffer.read(cx).snapshot(cx);
11127 let mut cursor_positions = Vec::new();
11128 for row_range in &row_ranges {
11129 let anchor = snapshot.anchor_before(Point::new(
11130 row_range.end.previous_row().0,
11131 snapshot.line_len(row_range.end.previous_row()),
11132 ));
11133 cursor_positions.push(anchor..anchor);
11134 }
11135
11136 self.transact(window, cx, |this, window, cx| {
11137 for row_range in row_ranges.into_iter().rev() {
11138 for row in row_range.iter_rows().rev() {
11139 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11140 let next_line_row = row.next_row();
11141 let indent = snapshot.indent_size_for_line(next_line_row);
11142 let start_of_next_line = Point::new(next_line_row.0, indent.len);
11143
11144 let replace =
11145 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
11146 " "
11147 } else {
11148 ""
11149 };
11150
11151 this.buffer.update(cx, |buffer, cx| {
11152 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11153 });
11154 }
11155 }
11156
11157 this.change_selections(Default::default(), window, cx, |s| {
11158 s.select_anchor_ranges(cursor_positions)
11159 });
11160 });
11161 }
11162
11163 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11164 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11165 self.join_lines_impl(true, window, cx);
11166 }
11167
11168 pub fn sort_lines_case_sensitive(
11169 &mut self,
11170 _: &SortLinesCaseSensitive,
11171 window: &mut Window,
11172 cx: &mut Context<Self>,
11173 ) {
11174 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11175 }
11176
11177 pub fn sort_lines_by_length(
11178 &mut self,
11179 _: &SortLinesByLength,
11180 window: &mut Window,
11181 cx: &mut Context<Self>,
11182 ) {
11183 self.manipulate_immutable_lines(window, cx, |lines| {
11184 lines.sort_by_key(|&line| line.chars().count())
11185 })
11186 }
11187
11188 pub fn sort_lines_case_insensitive(
11189 &mut self,
11190 _: &SortLinesCaseInsensitive,
11191 window: &mut Window,
11192 cx: &mut Context<Self>,
11193 ) {
11194 self.manipulate_immutable_lines(window, cx, |lines| {
11195 lines.sort_by_key(|line| line.to_lowercase())
11196 })
11197 }
11198
11199 pub fn unique_lines_case_insensitive(
11200 &mut self,
11201 _: &UniqueLinesCaseInsensitive,
11202 window: &mut Window,
11203 cx: &mut Context<Self>,
11204 ) {
11205 self.manipulate_immutable_lines(window, cx, |lines| {
11206 let mut seen = HashSet::default();
11207 lines.retain(|line| seen.insert(line.to_lowercase()));
11208 })
11209 }
11210
11211 pub fn unique_lines_case_sensitive(
11212 &mut self,
11213 _: &UniqueLinesCaseSensitive,
11214 window: &mut Window,
11215 cx: &mut Context<Self>,
11216 ) {
11217 self.manipulate_immutable_lines(window, cx, |lines| {
11218 let mut seen = HashSet::default();
11219 lines.retain(|line| seen.insert(*line));
11220 })
11221 }
11222
11223 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11224 let snapshot = self.buffer.read(cx).snapshot(cx);
11225 for selection in self.selections.disjoint_anchors_arc().iter() {
11226 if snapshot
11227 .language_at(selection.start)
11228 .and_then(|lang| lang.config().wrap_characters.as_ref())
11229 .is_some()
11230 {
11231 return true;
11232 }
11233 }
11234 false
11235 }
11236
11237 fn wrap_selections_in_tag(
11238 &mut self,
11239 _: &WrapSelectionsInTag,
11240 window: &mut Window,
11241 cx: &mut Context<Self>,
11242 ) {
11243 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11244
11245 let snapshot = self.buffer.read(cx).snapshot(cx);
11246
11247 let mut edits = Vec::new();
11248 let mut boundaries = Vec::new();
11249
11250 for selection in self
11251 .selections
11252 .all_adjusted(&self.display_snapshot(cx))
11253 .iter()
11254 {
11255 let Some(wrap_config) = snapshot
11256 .language_at(selection.start)
11257 .and_then(|lang| lang.config().wrap_characters.clone())
11258 else {
11259 continue;
11260 };
11261
11262 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11263 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11264
11265 let start_before = snapshot.anchor_before(selection.start);
11266 let end_after = snapshot.anchor_after(selection.end);
11267
11268 edits.push((start_before..start_before, open_tag));
11269 edits.push((end_after..end_after, close_tag));
11270
11271 boundaries.push((
11272 start_before,
11273 end_after,
11274 wrap_config.start_prefix.len(),
11275 wrap_config.end_suffix.len(),
11276 ));
11277 }
11278
11279 if edits.is_empty() {
11280 return;
11281 }
11282
11283 self.transact(window, cx, |this, window, cx| {
11284 let buffer = this.buffer.update(cx, |buffer, cx| {
11285 buffer.edit(edits, None, cx);
11286 buffer.snapshot(cx)
11287 });
11288
11289 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11290 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11291 boundaries.into_iter()
11292 {
11293 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11294 let close_offset = end_after
11295 .to_offset(&buffer)
11296 .saturating_sub_usize(end_suffix_len);
11297 new_selections.push(open_offset..open_offset);
11298 new_selections.push(close_offset..close_offset);
11299 }
11300
11301 this.change_selections(Default::default(), window, cx, |s| {
11302 s.select_ranges(new_selections);
11303 });
11304
11305 this.request_autoscroll(Autoscroll::fit(), cx);
11306 });
11307 }
11308
11309 pub fn toggle_read_only(
11310 &mut self,
11311 _: &workspace::ToggleReadOnlyFile,
11312 _: &mut Window,
11313 cx: &mut Context<Self>,
11314 ) {
11315 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11316 buffer.update(cx, |buffer, cx| {
11317 buffer.set_capability(
11318 match buffer.capability() {
11319 Capability::ReadWrite => Capability::Read,
11320 Capability::Read => Capability::ReadWrite,
11321 Capability::ReadOnly => Capability::ReadOnly,
11322 },
11323 cx,
11324 );
11325 })
11326 }
11327 }
11328
11329 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11330 let Some(project) = self.project.clone() else {
11331 return;
11332 };
11333 self.reload(project, window, cx)
11334 .detach_and_notify_err(window, cx);
11335 }
11336
11337 pub fn restore_file(
11338 &mut self,
11339 _: &::git::RestoreFile,
11340 window: &mut Window,
11341 cx: &mut Context<Self>,
11342 ) {
11343 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11344 let mut buffer_ids = HashSet::default();
11345 let snapshot = self.buffer().read(cx).snapshot(cx);
11346 for selection in self
11347 .selections
11348 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11349 {
11350 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11351 }
11352
11353 let buffer = self.buffer().read(cx);
11354 let ranges = buffer_ids
11355 .into_iter()
11356 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11357 .collect::<Vec<_>>();
11358
11359 self.restore_hunks_in_ranges(ranges, window, cx);
11360 }
11361
11362 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11363 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11364 let selections = self
11365 .selections
11366 .all(&self.display_snapshot(cx))
11367 .into_iter()
11368 .map(|s| s.range())
11369 .collect();
11370 self.restore_hunks_in_ranges(selections, window, cx);
11371 }
11372
11373 pub fn restore_hunks_in_ranges(
11374 &mut self,
11375 ranges: Vec<Range<Point>>,
11376 window: &mut Window,
11377 cx: &mut Context<Editor>,
11378 ) {
11379 let mut revert_changes = HashMap::default();
11380 let chunk_by = self
11381 .snapshot(window, cx)
11382 .hunks_for_ranges(ranges)
11383 .into_iter()
11384 .chunk_by(|hunk| hunk.buffer_id);
11385 for (buffer_id, hunks) in &chunk_by {
11386 let hunks = hunks.collect::<Vec<_>>();
11387 for hunk in &hunks {
11388 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11389 }
11390 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11391 }
11392 drop(chunk_by);
11393 if !revert_changes.is_empty() {
11394 self.transact(window, cx, |editor, window, cx| {
11395 editor.restore(revert_changes, window, cx);
11396 });
11397 }
11398 }
11399
11400 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11401 if let Some(status) = self
11402 .addons
11403 .iter()
11404 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11405 {
11406 return Some(status);
11407 }
11408 self.project
11409 .as_ref()?
11410 .read(cx)
11411 .status_for_buffer_id(buffer_id, cx)
11412 }
11413
11414 pub fn open_active_item_in_terminal(
11415 &mut self,
11416 _: &OpenInTerminal,
11417 window: &mut Window,
11418 cx: &mut Context<Self>,
11419 ) {
11420 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11421 let project_path = buffer.read(cx).project_path(cx)?;
11422 let project = self.project()?.read(cx);
11423 let entry = project.entry_for_path(&project_path, cx)?;
11424 let parent = match &entry.canonical_path {
11425 Some(canonical_path) => canonical_path.to_path_buf(),
11426 None => project.absolute_path(&project_path, cx)?,
11427 }
11428 .parent()?
11429 .to_path_buf();
11430 Some(parent)
11431 }) {
11432 window.dispatch_action(
11433 OpenTerminal {
11434 working_directory,
11435 local: false,
11436 }
11437 .boxed_clone(),
11438 cx,
11439 );
11440 }
11441 }
11442
11443 fn set_breakpoint_context_menu(
11444 &mut self,
11445 display_row: DisplayRow,
11446 position: Option<Anchor>,
11447 clicked_point: gpui::Point<Pixels>,
11448 window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 let source = self
11452 .buffer
11453 .read(cx)
11454 .snapshot(cx)
11455 .anchor_before(Point::new(display_row.0, 0u32));
11456
11457 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11458
11459 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11460 self,
11461 source,
11462 clicked_point,
11463 context_menu,
11464 window,
11465 cx,
11466 );
11467 }
11468
11469 fn add_edit_breakpoint_block(
11470 &mut self,
11471 anchor: Anchor,
11472 breakpoint: &Breakpoint,
11473 edit_action: BreakpointPromptEditAction,
11474 window: &mut Window,
11475 cx: &mut Context<Self>,
11476 ) {
11477 let weak_editor = cx.weak_entity();
11478 let bp_prompt = cx.new(|cx| {
11479 BreakpointPromptEditor::new(
11480 weak_editor,
11481 anchor,
11482 breakpoint.clone(),
11483 edit_action,
11484 window,
11485 cx,
11486 )
11487 });
11488
11489 let height = bp_prompt.update(cx, |this, cx| {
11490 this.prompt
11491 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11492 });
11493 let cloned_prompt = bp_prompt.clone();
11494 let blocks = vec![BlockProperties {
11495 style: BlockStyle::Sticky,
11496 placement: BlockPlacement::Above(anchor),
11497 height: Some(height),
11498 render: Arc::new(move |cx| {
11499 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11500 cloned_prompt.clone().into_any_element()
11501 }),
11502 priority: 0,
11503 }];
11504
11505 let focus_handle = bp_prompt.focus_handle(cx);
11506 window.focus(&focus_handle, cx);
11507
11508 let block_ids = self.insert_blocks(blocks, None, cx);
11509 bp_prompt.update(cx, |prompt, _| {
11510 prompt.add_block_ids(block_ids);
11511 });
11512 }
11513
11514 pub(crate) fn breakpoint_at_row(
11515 &self,
11516 row: u32,
11517 window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) -> Option<(Anchor, Breakpoint)> {
11520 let snapshot = self.snapshot(window, cx);
11521 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11522
11523 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11524 }
11525
11526 pub(crate) fn breakpoint_at_anchor(
11527 &self,
11528 breakpoint_position: Anchor,
11529 snapshot: &EditorSnapshot,
11530 cx: &mut Context<Self>,
11531 ) -> Option<(Anchor, Breakpoint)> {
11532 let buffer = self
11533 .buffer
11534 .read(cx)
11535 .buffer_for_anchor(breakpoint_position, cx)?;
11536
11537 let enclosing_excerpt = breakpoint_position.excerpt_id;
11538 let buffer_snapshot = buffer.read(cx).snapshot();
11539
11540 let row = buffer_snapshot
11541 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11542 .row;
11543
11544 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11545 let anchor_end = snapshot
11546 .buffer_snapshot()
11547 .anchor_after(Point::new(row, line_len));
11548
11549 self.breakpoint_store
11550 .as_ref()?
11551 .read_with(cx, |breakpoint_store, cx| {
11552 breakpoint_store
11553 .breakpoints(
11554 &buffer,
11555 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11556 &buffer_snapshot,
11557 cx,
11558 )
11559 .next()
11560 .and_then(|(bp, _)| {
11561 let breakpoint_row = buffer_snapshot
11562 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11563 .row;
11564
11565 if breakpoint_row == row {
11566 snapshot
11567 .buffer_snapshot()
11568 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11569 .map(|position| (position, bp.bp.clone()))
11570 } else {
11571 None
11572 }
11573 })
11574 })
11575 }
11576
11577 pub fn edit_log_breakpoint(
11578 &mut self,
11579 _: &EditLogBreakpoint,
11580 window: &mut Window,
11581 cx: &mut Context<Self>,
11582 ) {
11583 if self.breakpoint_store.is_none() {
11584 return;
11585 }
11586
11587 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11588 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11589 message: None,
11590 state: BreakpointState::Enabled,
11591 condition: None,
11592 hit_condition: None,
11593 });
11594
11595 self.add_edit_breakpoint_block(
11596 anchor,
11597 &breakpoint,
11598 BreakpointPromptEditAction::Log,
11599 window,
11600 cx,
11601 );
11602 }
11603 }
11604
11605 fn breakpoints_at_cursors(
11606 &self,
11607 window: &mut Window,
11608 cx: &mut Context<Self>,
11609 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11610 let snapshot = self.snapshot(window, cx);
11611 let cursors = self
11612 .selections
11613 .disjoint_anchors_arc()
11614 .iter()
11615 .map(|selection| {
11616 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11617
11618 let breakpoint_position = self
11619 .breakpoint_at_row(cursor_position.row, window, cx)
11620 .map(|bp| bp.0)
11621 .unwrap_or_else(|| {
11622 snapshot
11623 .display_snapshot
11624 .buffer_snapshot()
11625 .anchor_after(Point::new(cursor_position.row, 0))
11626 });
11627
11628 let breakpoint = self
11629 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11630 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11631
11632 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11633 })
11634 // 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.
11635 .collect::<HashMap<Anchor, _>>();
11636
11637 cursors.into_iter().collect()
11638 }
11639
11640 pub fn enable_breakpoint(
11641 &mut self,
11642 _: &crate::actions::EnableBreakpoint,
11643 window: &mut Window,
11644 cx: &mut Context<Self>,
11645 ) {
11646 if self.breakpoint_store.is_none() {
11647 return;
11648 }
11649
11650 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11651 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11652 continue;
11653 };
11654 self.edit_breakpoint_at_anchor(
11655 anchor,
11656 breakpoint,
11657 BreakpointEditAction::InvertState,
11658 cx,
11659 );
11660 }
11661 }
11662
11663 pub fn disable_breakpoint(
11664 &mut self,
11665 _: &crate::actions::DisableBreakpoint,
11666 window: &mut Window,
11667 cx: &mut Context<Self>,
11668 ) {
11669 if self.breakpoint_store.is_none() {
11670 return;
11671 }
11672
11673 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11674 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11675 continue;
11676 };
11677 self.edit_breakpoint_at_anchor(
11678 anchor,
11679 breakpoint,
11680 BreakpointEditAction::InvertState,
11681 cx,
11682 );
11683 }
11684 }
11685
11686 pub fn toggle_breakpoint(
11687 &mut self,
11688 _: &crate::actions::ToggleBreakpoint,
11689 window: &mut Window,
11690 cx: &mut Context<Self>,
11691 ) {
11692 if self.breakpoint_store.is_none() {
11693 return;
11694 }
11695
11696 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11697 if let Some(breakpoint) = breakpoint {
11698 self.edit_breakpoint_at_anchor(
11699 anchor,
11700 breakpoint,
11701 BreakpointEditAction::Toggle,
11702 cx,
11703 );
11704 } else {
11705 self.edit_breakpoint_at_anchor(
11706 anchor,
11707 Breakpoint::new_standard(),
11708 BreakpointEditAction::Toggle,
11709 cx,
11710 );
11711 }
11712 }
11713 }
11714
11715 pub fn edit_breakpoint_at_anchor(
11716 &mut self,
11717 breakpoint_position: Anchor,
11718 breakpoint: Breakpoint,
11719 edit_action: BreakpointEditAction,
11720 cx: &mut Context<Self>,
11721 ) {
11722 let Some(breakpoint_store) = &self.breakpoint_store else {
11723 return;
11724 };
11725
11726 let Some(buffer) = self
11727 .buffer
11728 .read(cx)
11729 .buffer_for_anchor(breakpoint_position, cx)
11730 else {
11731 return;
11732 };
11733
11734 breakpoint_store.update(cx, |breakpoint_store, cx| {
11735 breakpoint_store.toggle_breakpoint(
11736 buffer,
11737 BreakpointWithPosition {
11738 position: breakpoint_position.text_anchor,
11739 bp: breakpoint,
11740 },
11741 edit_action,
11742 cx,
11743 );
11744 });
11745
11746 cx.notify();
11747 }
11748
11749 #[cfg(any(test, feature = "test-support"))]
11750 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11751 self.breakpoint_store.clone()
11752 }
11753
11754 pub fn prepare_restore_change(
11755 &self,
11756 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11757 hunk: &MultiBufferDiffHunk,
11758 cx: &mut App,
11759 ) -> Option<()> {
11760 if hunk.is_created_file() {
11761 return None;
11762 }
11763 let buffer = self.buffer.read(cx);
11764 let diff = buffer.diff_for(hunk.buffer_id)?;
11765 let buffer = buffer.buffer(hunk.buffer_id)?;
11766 let buffer = buffer.read(cx);
11767 let original_text = diff
11768 .read(cx)
11769 .base_text(cx)
11770 .as_rope()
11771 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11772 let buffer_snapshot = buffer.snapshot();
11773 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11774 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11775 probe
11776 .0
11777 .start
11778 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11779 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11780 }) {
11781 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11782 Some(())
11783 } else {
11784 None
11785 }
11786 }
11787
11788 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11789 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11790 }
11791
11792 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11793 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11794 }
11795
11796 pub fn rotate_selections_forward(
11797 &mut self,
11798 _: &RotateSelectionsForward,
11799 window: &mut Window,
11800 cx: &mut Context<Self>,
11801 ) {
11802 self.rotate_selections(window, cx, false)
11803 }
11804
11805 pub fn rotate_selections_backward(
11806 &mut self,
11807 _: &RotateSelectionsBackward,
11808 window: &mut Window,
11809 cx: &mut Context<Self>,
11810 ) {
11811 self.rotate_selections(window, cx, true)
11812 }
11813
11814 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11816 let display_snapshot = self.display_snapshot(cx);
11817 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11818
11819 if selections.len() < 2 {
11820 return;
11821 }
11822
11823 let (edits, new_selections) = {
11824 let buffer = self.buffer.read(cx).read(cx);
11825 let has_selections = selections.iter().any(|s| !s.is_empty());
11826 if has_selections {
11827 let mut selected_texts: Vec<String> = selections
11828 .iter()
11829 .map(|selection| {
11830 buffer
11831 .text_for_range(selection.start..selection.end)
11832 .collect()
11833 })
11834 .collect();
11835
11836 if reverse {
11837 selected_texts.rotate_left(1);
11838 } else {
11839 selected_texts.rotate_right(1);
11840 }
11841
11842 let mut offset_delta: i64 = 0;
11843 let mut new_selections = Vec::new();
11844 let edits: Vec<_> = selections
11845 .iter()
11846 .zip(selected_texts.iter())
11847 .map(|(selection, new_text)| {
11848 let old_len = (selection.end.0 - selection.start.0) as i64;
11849 let new_len = new_text.len() as i64;
11850 let adjusted_start =
11851 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11852 let adjusted_end =
11853 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11854
11855 new_selections.push(Selection {
11856 id: selection.id,
11857 start: adjusted_start,
11858 end: adjusted_end,
11859 reversed: selection.reversed,
11860 goal: selection.goal,
11861 });
11862
11863 offset_delta += new_len - old_len;
11864 (selection.start..selection.end, new_text.clone())
11865 })
11866 .collect();
11867 (edits, new_selections)
11868 } else {
11869 let mut all_rows: Vec<u32> = selections
11870 .iter()
11871 .map(|selection| buffer.offset_to_point(selection.start).row)
11872 .collect();
11873 all_rows.sort_unstable();
11874 all_rows.dedup();
11875
11876 if all_rows.len() < 2 {
11877 return;
11878 }
11879
11880 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11881 .iter()
11882 .map(|&row| {
11883 let start = Point::new(row, 0);
11884 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11885 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11886 })
11887 .collect();
11888
11889 let mut line_texts: Vec<String> = line_ranges
11890 .iter()
11891 .map(|range| buffer.text_for_range(range.clone()).collect())
11892 .collect();
11893
11894 if reverse {
11895 line_texts.rotate_left(1);
11896 } else {
11897 line_texts.rotate_right(1);
11898 }
11899
11900 let edits = line_ranges
11901 .iter()
11902 .zip(line_texts.iter())
11903 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11904 .collect();
11905
11906 let num_rows = all_rows.len();
11907 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11908 .iter()
11909 .enumerate()
11910 .map(|(i, &row)| (row, i))
11911 .collect();
11912
11913 // Compute new line start offsets after rotation (handles CRLF)
11914 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11915 let first_line_start = line_ranges[0].start.0;
11916 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11917 for text in line_texts.iter().take(num_rows - 1) {
11918 let prev_start = *new_line_starts.last().unwrap();
11919 new_line_starts.push(prev_start + text.len() + newline_len);
11920 }
11921
11922 let new_selections = selections
11923 .iter()
11924 .map(|selection| {
11925 let point = buffer.offset_to_point(selection.start);
11926 let old_index = row_to_index[&point.row];
11927 let new_index = if reverse {
11928 (old_index + num_rows - 1) % num_rows
11929 } else {
11930 (old_index + 1) % num_rows
11931 };
11932 let new_offset =
11933 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11934 Selection {
11935 id: selection.id,
11936 start: new_offset,
11937 end: new_offset,
11938 reversed: selection.reversed,
11939 goal: selection.goal,
11940 }
11941 })
11942 .collect();
11943
11944 (edits, new_selections)
11945 }
11946 };
11947
11948 self.transact(window, cx, |this, window, cx| {
11949 this.buffer.update(cx, |buffer, cx| {
11950 buffer.edit(edits, None, cx);
11951 });
11952 this.change_selections(Default::default(), window, cx, |s| {
11953 s.select(new_selections);
11954 });
11955 });
11956 }
11957
11958 fn manipulate_lines<M>(
11959 &mut self,
11960 window: &mut Window,
11961 cx: &mut Context<Self>,
11962 mut manipulate: M,
11963 ) where
11964 M: FnMut(&str) -> LineManipulationResult,
11965 {
11966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11967
11968 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11969 let buffer = self.buffer.read(cx).snapshot(cx);
11970
11971 let mut edits = Vec::new();
11972
11973 let selections = self.selections.all::<Point>(&display_map);
11974 let mut selections = selections.iter().peekable();
11975 let mut contiguous_row_selections = Vec::new();
11976 let mut new_selections = Vec::new();
11977 let mut added_lines = 0;
11978 let mut removed_lines = 0;
11979
11980 while let Some(selection) = selections.next() {
11981 let (start_row, end_row) = consume_contiguous_rows(
11982 &mut contiguous_row_selections,
11983 selection,
11984 &display_map,
11985 &mut selections,
11986 );
11987
11988 let start_point = Point::new(start_row.0, 0);
11989 let end_point = Point::new(
11990 end_row.previous_row().0,
11991 buffer.line_len(end_row.previous_row()),
11992 );
11993 let text = buffer
11994 .text_for_range(start_point..end_point)
11995 .collect::<String>();
11996
11997 let LineManipulationResult {
11998 new_text,
11999 line_count_before,
12000 line_count_after,
12001 } = manipulate(&text);
12002
12003 edits.push((start_point..end_point, new_text));
12004
12005 // Selections must change based on added and removed line count
12006 let start_row =
12007 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12008 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12009 new_selections.push(Selection {
12010 id: selection.id,
12011 start: start_row,
12012 end: end_row,
12013 goal: SelectionGoal::None,
12014 reversed: selection.reversed,
12015 });
12016
12017 if line_count_after > line_count_before {
12018 added_lines += line_count_after - line_count_before;
12019 } else if line_count_before > line_count_after {
12020 removed_lines += line_count_before - line_count_after;
12021 }
12022 }
12023
12024 self.transact(window, cx, |this, window, cx| {
12025 let buffer = this.buffer.update(cx, |buffer, cx| {
12026 buffer.edit(edits, None, cx);
12027 buffer.snapshot(cx)
12028 });
12029
12030 // Recalculate offsets on newly edited buffer
12031 let new_selections = new_selections
12032 .iter()
12033 .map(|s| {
12034 let start_point = Point::new(s.start.0, 0);
12035 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12036 Selection {
12037 id: s.id,
12038 start: buffer.point_to_offset(start_point),
12039 end: buffer.point_to_offset(end_point),
12040 goal: s.goal,
12041 reversed: s.reversed,
12042 }
12043 })
12044 .collect();
12045
12046 this.change_selections(Default::default(), window, cx, |s| {
12047 s.select(new_selections);
12048 });
12049
12050 this.request_autoscroll(Autoscroll::fit(), cx);
12051 });
12052 }
12053
12054 fn manipulate_immutable_lines<Fn>(
12055 &mut self,
12056 window: &mut Window,
12057 cx: &mut Context<Self>,
12058 mut callback: Fn,
12059 ) where
12060 Fn: FnMut(&mut Vec<&str>),
12061 {
12062 self.manipulate_lines(window, cx, |text| {
12063 let mut lines: Vec<&str> = text.split('\n').collect();
12064 let line_count_before = lines.len();
12065
12066 callback(&mut lines);
12067
12068 LineManipulationResult {
12069 new_text: lines.join("\n"),
12070 line_count_before,
12071 line_count_after: lines.len(),
12072 }
12073 });
12074 }
12075
12076 fn manipulate_mutable_lines<Fn>(
12077 &mut self,
12078 window: &mut Window,
12079 cx: &mut Context<Self>,
12080 mut callback: Fn,
12081 ) where
12082 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12083 {
12084 self.manipulate_lines(window, cx, |text| {
12085 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12086 let line_count_before = lines.len();
12087
12088 callback(&mut lines);
12089
12090 LineManipulationResult {
12091 new_text: lines.join("\n"),
12092 line_count_before,
12093 line_count_after: lines.len(),
12094 }
12095 });
12096 }
12097
12098 pub fn convert_indentation_to_spaces(
12099 &mut self,
12100 _: &ConvertIndentationToSpaces,
12101 window: &mut Window,
12102 cx: &mut Context<Self>,
12103 ) {
12104 let settings = self.buffer.read(cx).language_settings(cx);
12105 let tab_size = settings.tab_size.get() as usize;
12106
12107 self.manipulate_mutable_lines(window, cx, |lines| {
12108 // Allocates a reasonably sized scratch buffer once for the whole loop
12109 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12110 // Avoids recomputing spaces that could be inserted many times
12111 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12112 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12113 .collect();
12114
12115 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12116 let mut chars = line.as_ref().chars();
12117 let mut col = 0;
12118 let mut changed = false;
12119
12120 for ch in chars.by_ref() {
12121 match ch {
12122 ' ' => {
12123 reindented_line.push(' ');
12124 col += 1;
12125 }
12126 '\t' => {
12127 // \t are converted to spaces depending on the current column
12128 let spaces_len = tab_size - (col % tab_size);
12129 reindented_line.extend(&space_cache[spaces_len - 1]);
12130 col += spaces_len;
12131 changed = true;
12132 }
12133 _ => {
12134 // If we dont append before break, the character is consumed
12135 reindented_line.push(ch);
12136 break;
12137 }
12138 }
12139 }
12140
12141 if !changed {
12142 reindented_line.clear();
12143 continue;
12144 }
12145 // Append the rest of the line and replace old reference with new one
12146 reindented_line.extend(chars);
12147 *line = Cow::Owned(reindented_line.clone());
12148 reindented_line.clear();
12149 }
12150 });
12151 }
12152
12153 pub fn convert_indentation_to_tabs(
12154 &mut self,
12155 _: &ConvertIndentationToTabs,
12156 window: &mut Window,
12157 cx: &mut Context<Self>,
12158 ) {
12159 let settings = self.buffer.read(cx).language_settings(cx);
12160 let tab_size = settings.tab_size.get() as usize;
12161
12162 self.manipulate_mutable_lines(window, cx, |lines| {
12163 // Allocates a reasonably sized buffer once for the whole loop
12164 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12165 // Avoids recomputing spaces that could be inserted many times
12166 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12167 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12168 .collect();
12169
12170 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12171 let mut chars = line.chars();
12172 let mut spaces_count = 0;
12173 let mut first_non_indent_char = None;
12174 let mut changed = false;
12175
12176 for ch in chars.by_ref() {
12177 match ch {
12178 ' ' => {
12179 // Keep track of spaces. Append \t when we reach tab_size
12180 spaces_count += 1;
12181 changed = true;
12182 if spaces_count == tab_size {
12183 reindented_line.push('\t');
12184 spaces_count = 0;
12185 }
12186 }
12187 '\t' => {
12188 reindented_line.push('\t');
12189 spaces_count = 0;
12190 }
12191 _ => {
12192 // Dont append it yet, we might have remaining spaces
12193 first_non_indent_char = Some(ch);
12194 break;
12195 }
12196 }
12197 }
12198
12199 if !changed {
12200 reindented_line.clear();
12201 continue;
12202 }
12203 // Remaining spaces that didn't make a full tab stop
12204 if spaces_count > 0 {
12205 reindented_line.extend(&space_cache[spaces_count - 1]);
12206 }
12207 // If we consume an extra character that was not indentation, add it back
12208 if let Some(extra_char) = first_non_indent_char {
12209 reindented_line.push(extra_char);
12210 }
12211 // Append the rest of the line and replace old reference with new one
12212 reindented_line.extend(chars);
12213 *line = Cow::Owned(reindented_line.clone());
12214 reindented_line.clear();
12215 }
12216 });
12217 }
12218
12219 pub fn convert_to_upper_case(
12220 &mut self,
12221 _: &ConvertToUpperCase,
12222 window: &mut Window,
12223 cx: &mut Context<Self>,
12224 ) {
12225 self.manipulate_text(window, cx, |text| text.to_uppercase())
12226 }
12227
12228 pub fn convert_to_lower_case(
12229 &mut self,
12230 _: &ConvertToLowerCase,
12231 window: &mut Window,
12232 cx: &mut Context<Self>,
12233 ) {
12234 self.manipulate_text(window, cx, |text| text.to_lowercase())
12235 }
12236
12237 pub fn convert_to_title_case(
12238 &mut self,
12239 _: &ConvertToTitleCase,
12240 window: &mut Window,
12241 cx: &mut Context<Self>,
12242 ) {
12243 self.manipulate_text(window, cx, |text| {
12244 text.split('\n')
12245 .map(|line| line.to_case(Case::Title))
12246 .join("\n")
12247 })
12248 }
12249
12250 pub fn convert_to_snake_case(
12251 &mut self,
12252 _: &ConvertToSnakeCase,
12253 window: &mut Window,
12254 cx: &mut Context<Self>,
12255 ) {
12256 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12257 }
12258
12259 pub fn convert_to_kebab_case(
12260 &mut self,
12261 _: &ConvertToKebabCase,
12262 window: &mut Window,
12263 cx: &mut Context<Self>,
12264 ) {
12265 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12266 }
12267
12268 pub fn convert_to_upper_camel_case(
12269 &mut self,
12270 _: &ConvertToUpperCamelCase,
12271 window: &mut Window,
12272 cx: &mut Context<Self>,
12273 ) {
12274 self.manipulate_text(window, cx, |text| {
12275 text.split('\n')
12276 .map(|line| line.to_case(Case::UpperCamel))
12277 .join("\n")
12278 })
12279 }
12280
12281 pub fn convert_to_lower_camel_case(
12282 &mut self,
12283 _: &ConvertToLowerCamelCase,
12284 window: &mut Window,
12285 cx: &mut Context<Self>,
12286 ) {
12287 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12288 }
12289
12290 pub fn convert_to_opposite_case(
12291 &mut self,
12292 _: &ConvertToOppositeCase,
12293 window: &mut Window,
12294 cx: &mut Context<Self>,
12295 ) {
12296 self.manipulate_text(window, cx, |text| {
12297 text.chars()
12298 .fold(String::with_capacity(text.len()), |mut t, c| {
12299 if c.is_uppercase() {
12300 t.extend(c.to_lowercase());
12301 } else {
12302 t.extend(c.to_uppercase());
12303 }
12304 t
12305 })
12306 })
12307 }
12308
12309 pub fn convert_to_sentence_case(
12310 &mut self,
12311 _: &ConvertToSentenceCase,
12312 window: &mut Window,
12313 cx: &mut Context<Self>,
12314 ) {
12315 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12316 }
12317
12318 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12319 self.manipulate_text(window, cx, |text| {
12320 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12321 if has_upper_case_characters {
12322 text.to_lowercase()
12323 } else {
12324 text.to_uppercase()
12325 }
12326 })
12327 }
12328
12329 pub fn convert_to_rot13(
12330 &mut self,
12331 _: &ConvertToRot13,
12332 window: &mut Window,
12333 cx: &mut Context<Self>,
12334 ) {
12335 self.manipulate_text(window, cx, |text| {
12336 text.chars()
12337 .map(|c| match c {
12338 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12339 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12340 _ => c,
12341 })
12342 .collect()
12343 })
12344 }
12345
12346 pub fn convert_to_rot47(
12347 &mut self,
12348 _: &ConvertToRot47,
12349 window: &mut Window,
12350 cx: &mut Context<Self>,
12351 ) {
12352 self.manipulate_text(window, cx, |text| {
12353 text.chars()
12354 .map(|c| {
12355 let code_point = c as u32;
12356 if code_point >= 33 && code_point <= 126 {
12357 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12358 }
12359 c
12360 })
12361 .collect()
12362 })
12363 }
12364
12365 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12366 where
12367 Fn: FnMut(&str) -> String,
12368 {
12369 let buffer = self.buffer.read(cx).snapshot(cx);
12370
12371 let mut new_selections = Vec::new();
12372 let mut edits = Vec::new();
12373 let mut selection_adjustment = 0isize;
12374
12375 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12376 let selection_is_empty = selection.is_empty();
12377
12378 let (start, end) = if selection_is_empty {
12379 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12380 (word_range.start, word_range.end)
12381 } else {
12382 (
12383 buffer.point_to_offset(selection.start),
12384 buffer.point_to_offset(selection.end),
12385 )
12386 };
12387
12388 let text = buffer.text_for_range(start..end).collect::<String>();
12389 let old_length = text.len() as isize;
12390 let text = callback(&text);
12391
12392 new_selections.push(Selection {
12393 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12394 end: MultiBufferOffset(
12395 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12396 ),
12397 goal: SelectionGoal::None,
12398 id: selection.id,
12399 reversed: selection.reversed,
12400 });
12401
12402 selection_adjustment += old_length - text.len() as isize;
12403
12404 edits.push((start..end, text));
12405 }
12406
12407 self.transact(window, cx, |this, window, cx| {
12408 this.buffer.update(cx, |buffer, cx| {
12409 buffer.edit(edits, None, cx);
12410 });
12411
12412 this.change_selections(Default::default(), window, cx, |s| {
12413 s.select(new_selections);
12414 });
12415
12416 this.request_autoscroll(Autoscroll::fit(), cx);
12417 });
12418 }
12419
12420 pub fn move_selection_on_drop(
12421 &mut self,
12422 selection: &Selection<Anchor>,
12423 target: DisplayPoint,
12424 is_cut: bool,
12425 window: &mut Window,
12426 cx: &mut Context<Self>,
12427 ) {
12428 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12429 let buffer = display_map.buffer_snapshot();
12430 let mut edits = Vec::new();
12431 let insert_point = display_map
12432 .clip_point(target, Bias::Left)
12433 .to_point(&display_map);
12434 let text = buffer
12435 .text_for_range(selection.start..selection.end)
12436 .collect::<String>();
12437 if is_cut {
12438 edits.push(((selection.start..selection.end), String::new()));
12439 }
12440 let insert_anchor = buffer.anchor_before(insert_point);
12441 edits.push(((insert_anchor..insert_anchor), text));
12442 let last_edit_start = insert_anchor.bias_left(buffer);
12443 let last_edit_end = insert_anchor.bias_right(buffer);
12444 self.transact(window, cx, |this, window, cx| {
12445 this.buffer.update(cx, |buffer, cx| {
12446 buffer.edit(edits, None, cx);
12447 });
12448 this.change_selections(Default::default(), window, cx, |s| {
12449 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12450 });
12451 });
12452 }
12453
12454 pub fn clear_selection_drag_state(&mut self) {
12455 self.selection_drag_state = SelectionDragState::None;
12456 }
12457
12458 pub fn duplicate(
12459 &mut self,
12460 upwards: bool,
12461 whole_lines: bool,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) {
12465 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12466
12467 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12468 let buffer = display_map.buffer_snapshot();
12469 let selections = self.selections.all::<Point>(&display_map);
12470
12471 let mut edits = Vec::new();
12472 let mut selections_iter = selections.iter().peekable();
12473 while let Some(selection) = selections_iter.next() {
12474 let mut rows = selection.spanned_rows(false, &display_map);
12475 // duplicate line-wise
12476 if whole_lines || selection.start == selection.end {
12477 // Avoid duplicating the same lines twice.
12478 while let Some(next_selection) = selections_iter.peek() {
12479 let next_rows = next_selection.spanned_rows(false, &display_map);
12480 if next_rows.start < rows.end {
12481 rows.end = next_rows.end;
12482 selections_iter.next().unwrap();
12483 } else {
12484 break;
12485 }
12486 }
12487
12488 // Copy the text from the selected row region and splice it either at the start
12489 // or end of the region.
12490 let start = Point::new(rows.start.0, 0);
12491 let end = Point::new(
12492 rows.end.previous_row().0,
12493 buffer.line_len(rows.end.previous_row()),
12494 );
12495
12496 let mut text = buffer.text_for_range(start..end).collect::<String>();
12497
12498 let insert_location = if upwards {
12499 // When duplicating upward, we need to insert before the current line.
12500 // If we're on the last line and it doesn't end with a newline,
12501 // we need to add a newline before the duplicated content.
12502 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12503 && buffer.max_point().column > 0
12504 && !text.ends_with('\n');
12505
12506 if needs_leading_newline {
12507 text.insert(0, '\n');
12508 end
12509 } else {
12510 text.push('\n');
12511 Point::new(rows.start.0, 0)
12512 }
12513 } else {
12514 text.push('\n');
12515 start
12516 };
12517 edits.push((insert_location..insert_location, text));
12518 } else {
12519 // duplicate character-wise
12520 let start = selection.start;
12521 let end = selection.end;
12522 let text = buffer.text_for_range(start..end).collect::<String>();
12523 edits.push((selection.end..selection.end, text));
12524 }
12525 }
12526
12527 self.transact(window, cx, |this, window, cx| {
12528 this.buffer.update(cx, |buffer, cx| {
12529 buffer.edit(edits, None, cx);
12530 });
12531
12532 // When duplicating upward with whole lines, move the cursor to the duplicated line
12533 if upwards && whole_lines {
12534 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12535
12536 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12537 let mut new_ranges = Vec::new();
12538 let selections = s.all::<Point>(&display_map);
12539 let mut selections_iter = selections.iter().peekable();
12540
12541 while let Some(first_selection) = selections_iter.next() {
12542 // Group contiguous selections together to find the total row span
12543 let mut group_selections = vec![first_selection];
12544 let mut rows = first_selection.spanned_rows(false, &display_map);
12545
12546 while let Some(next_selection) = selections_iter.peek() {
12547 let next_rows = next_selection.spanned_rows(false, &display_map);
12548 if next_rows.start < rows.end {
12549 rows.end = next_rows.end;
12550 group_selections.push(selections_iter.next().unwrap());
12551 } else {
12552 break;
12553 }
12554 }
12555
12556 let row_count = rows.end.0 - rows.start.0;
12557
12558 // Move all selections in this group up by the total number of duplicated rows
12559 for selection in group_selections {
12560 let new_start = Point::new(
12561 selection.start.row.saturating_sub(row_count),
12562 selection.start.column,
12563 );
12564
12565 let new_end = Point::new(
12566 selection.end.row.saturating_sub(row_count),
12567 selection.end.column,
12568 );
12569
12570 new_ranges.push(new_start..new_end);
12571 }
12572 }
12573
12574 s.select_ranges(new_ranges);
12575 });
12576 }
12577
12578 this.request_autoscroll(Autoscroll::fit(), cx);
12579 });
12580 }
12581
12582 pub fn duplicate_line_up(
12583 &mut self,
12584 _: &DuplicateLineUp,
12585 window: &mut Window,
12586 cx: &mut Context<Self>,
12587 ) {
12588 self.duplicate(true, true, window, cx);
12589 }
12590
12591 pub fn duplicate_line_down(
12592 &mut self,
12593 _: &DuplicateLineDown,
12594 window: &mut Window,
12595 cx: &mut Context<Self>,
12596 ) {
12597 self.duplicate(false, true, window, cx);
12598 }
12599
12600 pub fn duplicate_selection(
12601 &mut self,
12602 _: &DuplicateSelection,
12603 window: &mut Window,
12604 cx: &mut Context<Self>,
12605 ) {
12606 self.duplicate(false, false, window, cx);
12607 }
12608
12609 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12611 if self.mode.is_single_line() {
12612 cx.propagate();
12613 return;
12614 }
12615
12616 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12617 let buffer = self.buffer.read(cx).snapshot(cx);
12618
12619 let mut edits = Vec::new();
12620 let mut unfold_ranges = Vec::new();
12621 let mut refold_creases = Vec::new();
12622
12623 let selections = self.selections.all::<Point>(&display_map);
12624 let mut selections = selections.iter().peekable();
12625 let mut contiguous_row_selections = Vec::new();
12626 let mut new_selections = Vec::new();
12627
12628 while let Some(selection) = selections.next() {
12629 // Find all the selections that span a contiguous row range
12630 let (start_row, end_row) = consume_contiguous_rows(
12631 &mut contiguous_row_selections,
12632 selection,
12633 &display_map,
12634 &mut selections,
12635 );
12636
12637 // Move the text spanned by the row range to be before the line preceding the row range
12638 if start_row.0 > 0 {
12639 let range_to_move = Point::new(
12640 start_row.previous_row().0,
12641 buffer.line_len(start_row.previous_row()),
12642 )
12643 ..Point::new(
12644 end_row.previous_row().0,
12645 buffer.line_len(end_row.previous_row()),
12646 );
12647 let insertion_point = display_map
12648 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12649 .0;
12650
12651 // Don't move lines across excerpts
12652 if buffer
12653 .excerpt_containing(insertion_point..range_to_move.end)
12654 .is_some()
12655 {
12656 let text = buffer
12657 .text_for_range(range_to_move.clone())
12658 .flat_map(|s| s.chars())
12659 .skip(1)
12660 .chain(['\n'])
12661 .collect::<String>();
12662
12663 edits.push((
12664 buffer.anchor_after(range_to_move.start)
12665 ..buffer.anchor_before(range_to_move.end),
12666 String::new(),
12667 ));
12668 let insertion_anchor = buffer.anchor_after(insertion_point);
12669 edits.push((insertion_anchor..insertion_anchor, text));
12670
12671 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12672
12673 // Move selections up
12674 new_selections.extend(contiguous_row_selections.drain(..).map(
12675 |mut selection| {
12676 selection.start.row -= row_delta;
12677 selection.end.row -= row_delta;
12678 selection
12679 },
12680 ));
12681
12682 // Move folds up
12683 unfold_ranges.push(range_to_move.clone());
12684 for fold in display_map.folds_in_range(
12685 buffer.anchor_before(range_to_move.start)
12686 ..buffer.anchor_after(range_to_move.end),
12687 ) {
12688 let mut start = fold.range.start.to_point(&buffer);
12689 let mut end = fold.range.end.to_point(&buffer);
12690 start.row -= row_delta;
12691 end.row -= row_delta;
12692 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12693 }
12694 }
12695 }
12696
12697 // If we didn't move line(s), preserve the existing selections
12698 new_selections.append(&mut contiguous_row_selections);
12699 }
12700
12701 self.transact(window, cx, |this, window, cx| {
12702 this.unfold_ranges(&unfold_ranges, true, true, cx);
12703 this.buffer.update(cx, |buffer, cx| {
12704 for (range, text) in edits {
12705 buffer.edit([(range, text)], None, cx);
12706 }
12707 });
12708 this.fold_creases(refold_creases, true, window, cx);
12709 this.change_selections(Default::default(), window, cx, |s| {
12710 s.select(new_selections);
12711 })
12712 });
12713 }
12714
12715 pub fn move_line_down(
12716 &mut self,
12717 _: &MoveLineDown,
12718 window: &mut Window,
12719 cx: &mut Context<Self>,
12720 ) {
12721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12722 if self.mode.is_single_line() {
12723 cx.propagate();
12724 return;
12725 }
12726
12727 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12728 let buffer = self.buffer.read(cx).snapshot(cx);
12729
12730 let mut edits = Vec::new();
12731 let mut unfold_ranges = Vec::new();
12732 let mut refold_creases = Vec::new();
12733
12734 let selections = self.selections.all::<Point>(&display_map);
12735 let mut selections = selections.iter().peekable();
12736 let mut contiguous_row_selections = Vec::new();
12737 let mut new_selections = Vec::new();
12738
12739 while let Some(selection) = selections.next() {
12740 // Find all the selections that span a contiguous row range
12741 let (start_row, end_row) = consume_contiguous_rows(
12742 &mut contiguous_row_selections,
12743 selection,
12744 &display_map,
12745 &mut selections,
12746 );
12747
12748 // Move the text spanned by the row range to be after the last line of the row range
12749 if end_row.0 <= buffer.max_point().row {
12750 let range_to_move =
12751 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12752 let insertion_point = display_map
12753 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12754 .0;
12755
12756 // Don't move lines across excerpt boundaries
12757 if buffer
12758 .excerpt_containing(range_to_move.start..insertion_point)
12759 .is_some()
12760 {
12761 let mut text = String::from("\n");
12762 text.extend(buffer.text_for_range(range_to_move.clone()));
12763 text.pop(); // Drop trailing newline
12764 edits.push((
12765 buffer.anchor_after(range_to_move.start)
12766 ..buffer.anchor_before(range_to_move.end),
12767 String::new(),
12768 ));
12769 let insertion_anchor = buffer.anchor_after(insertion_point);
12770 edits.push((insertion_anchor..insertion_anchor, text));
12771
12772 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12773
12774 // Move selections down
12775 new_selections.extend(contiguous_row_selections.drain(..).map(
12776 |mut selection| {
12777 selection.start.row += row_delta;
12778 selection.end.row += row_delta;
12779 selection
12780 },
12781 ));
12782
12783 // Move folds down
12784 unfold_ranges.push(range_to_move.clone());
12785 for fold in display_map.folds_in_range(
12786 buffer.anchor_before(range_to_move.start)
12787 ..buffer.anchor_after(range_to_move.end),
12788 ) {
12789 let mut start = fold.range.start.to_point(&buffer);
12790 let mut end = fold.range.end.to_point(&buffer);
12791 start.row += row_delta;
12792 end.row += row_delta;
12793 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12794 }
12795 }
12796 }
12797
12798 // If we didn't move line(s), preserve the existing selections
12799 new_selections.append(&mut contiguous_row_selections);
12800 }
12801
12802 self.transact(window, cx, |this, window, cx| {
12803 this.unfold_ranges(&unfold_ranges, true, true, cx);
12804 this.buffer.update(cx, |buffer, cx| {
12805 for (range, text) in edits {
12806 buffer.edit([(range, text)], None, cx);
12807 }
12808 });
12809 this.fold_creases(refold_creases, true, window, cx);
12810 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12811 });
12812 }
12813
12814 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12816 let text_layout_details = &self.text_layout_details(window);
12817 self.transact(window, cx, |this, window, cx| {
12818 let edits = this.change_selections(Default::default(), window, cx, |s| {
12819 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12820 s.move_with(|display_map, selection| {
12821 if !selection.is_empty() {
12822 return;
12823 }
12824
12825 let mut head = selection.head();
12826 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12827 if head.column() == display_map.line_len(head.row()) {
12828 transpose_offset = display_map
12829 .buffer_snapshot()
12830 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12831 }
12832
12833 if transpose_offset == MultiBufferOffset(0) {
12834 return;
12835 }
12836
12837 *head.column_mut() += 1;
12838 head = display_map.clip_point(head, Bias::Right);
12839 let goal = SelectionGoal::HorizontalPosition(
12840 display_map
12841 .x_for_display_point(head, text_layout_details)
12842 .into(),
12843 );
12844 selection.collapse_to(head, goal);
12845
12846 let transpose_start = display_map
12847 .buffer_snapshot()
12848 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12849 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12850 let transpose_end = display_map
12851 .buffer_snapshot()
12852 .clip_offset(transpose_offset + 1usize, Bias::Right);
12853 if let Some(ch) = display_map
12854 .buffer_snapshot()
12855 .chars_at(transpose_start)
12856 .next()
12857 {
12858 edits.push((transpose_start..transpose_offset, String::new()));
12859 edits.push((transpose_end..transpose_end, ch.to_string()));
12860 }
12861 }
12862 });
12863 edits
12864 });
12865 this.buffer
12866 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12867 let selections = this
12868 .selections
12869 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12870 this.change_selections(Default::default(), window, cx, |s| {
12871 s.select(selections);
12872 });
12873 });
12874 }
12875
12876 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12878 if self.mode.is_single_line() {
12879 cx.propagate();
12880 return;
12881 }
12882
12883 self.rewrap_impl(RewrapOptions::default(), cx)
12884 }
12885
12886 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12887 let buffer = self.buffer.read(cx).snapshot(cx);
12888 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12889
12890 #[derive(Clone, Debug, PartialEq)]
12891 enum CommentFormat {
12892 /// single line comment, with prefix for line
12893 Line(String),
12894 /// single line within a block comment, with prefix for line
12895 BlockLine(String),
12896 /// a single line of a block comment that includes the initial delimiter
12897 BlockCommentWithStart(BlockCommentConfig),
12898 /// a single line of a block comment that includes the ending delimiter
12899 BlockCommentWithEnd(BlockCommentConfig),
12900 }
12901
12902 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12903 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12904 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12905 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12906 .peekable();
12907
12908 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12909 row
12910 } else {
12911 return Vec::new();
12912 };
12913
12914 let language_settings = buffer.language_settings_at(selection.head(), cx);
12915 let language_scope = buffer.language_scope_at(selection.head());
12916
12917 let indent_and_prefix_for_row =
12918 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12919 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12920 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12921 &language_scope
12922 {
12923 let indent_end = Point::new(row, indent.len);
12924 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12925 let line_text_after_indent = buffer
12926 .text_for_range(indent_end..line_end)
12927 .collect::<String>();
12928
12929 let is_within_comment_override = buffer
12930 .language_scope_at(indent_end)
12931 .is_some_and(|scope| scope.override_name() == Some("comment"));
12932 let comment_delimiters = if is_within_comment_override {
12933 // we are within a comment syntax node, but we don't
12934 // yet know what kind of comment: block, doc or line
12935 match (
12936 language_scope.documentation_comment(),
12937 language_scope.block_comment(),
12938 ) {
12939 (Some(config), _) | (_, Some(config))
12940 if buffer.contains_str_at(indent_end, &config.start) =>
12941 {
12942 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12943 }
12944 (Some(config), _) | (_, Some(config))
12945 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12946 {
12947 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12948 }
12949 (Some(config), _) | (_, Some(config))
12950 if buffer.contains_str_at(indent_end, &config.prefix) =>
12951 {
12952 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12953 }
12954 (_, _) => language_scope
12955 .line_comment_prefixes()
12956 .iter()
12957 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12958 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12959 }
12960 } else {
12961 // we not in an overridden comment node, but we may
12962 // be within a non-overridden line comment node
12963 language_scope
12964 .line_comment_prefixes()
12965 .iter()
12966 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12967 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12968 };
12969
12970 let rewrap_prefix = language_scope
12971 .rewrap_prefixes()
12972 .iter()
12973 .find_map(|prefix_regex| {
12974 prefix_regex.find(&line_text_after_indent).map(|mat| {
12975 if mat.start() == 0 {
12976 Some(mat.as_str().to_string())
12977 } else {
12978 None
12979 }
12980 })
12981 })
12982 .flatten();
12983 (comment_delimiters, rewrap_prefix)
12984 } else {
12985 (None, None)
12986 };
12987 (indent, comment_prefix, rewrap_prefix)
12988 };
12989
12990 let mut ranges = Vec::new();
12991 let from_empty_selection = selection.is_empty();
12992
12993 let mut current_range_start = first_row;
12994 let mut prev_row = first_row;
12995 let (
12996 mut current_range_indent,
12997 mut current_range_comment_delimiters,
12998 mut current_range_rewrap_prefix,
12999 ) = indent_and_prefix_for_row(first_row);
13000
13001 for row in non_blank_rows_iter.skip(1) {
13002 let has_paragraph_break = row > prev_row + 1;
13003
13004 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13005 indent_and_prefix_for_row(row);
13006
13007 let has_indent_change = row_indent != current_range_indent;
13008 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13009
13010 let has_boundary_change = has_comment_change
13011 || row_rewrap_prefix.is_some()
13012 || (has_indent_change && current_range_comment_delimiters.is_some());
13013
13014 if has_paragraph_break || has_boundary_change {
13015 ranges.push((
13016 language_settings.clone(),
13017 Point::new(current_range_start, 0)
13018 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13019 current_range_indent,
13020 current_range_comment_delimiters.clone(),
13021 current_range_rewrap_prefix.clone(),
13022 from_empty_selection,
13023 ));
13024 current_range_start = row;
13025 current_range_indent = row_indent;
13026 current_range_comment_delimiters = row_comment_delimiters;
13027 current_range_rewrap_prefix = row_rewrap_prefix;
13028 }
13029 prev_row = row;
13030 }
13031
13032 ranges.push((
13033 language_settings.clone(),
13034 Point::new(current_range_start, 0)
13035 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13036 current_range_indent,
13037 current_range_comment_delimiters,
13038 current_range_rewrap_prefix,
13039 from_empty_selection,
13040 ));
13041
13042 ranges
13043 });
13044
13045 let mut edits = Vec::new();
13046 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13047
13048 for (
13049 language_settings,
13050 wrap_range,
13051 mut indent_size,
13052 comment_prefix,
13053 rewrap_prefix,
13054 from_empty_selection,
13055 ) in wrap_ranges
13056 {
13057 let mut start_row = wrap_range.start.row;
13058 let mut end_row = wrap_range.end.row;
13059
13060 // Skip selections that overlap with a range that has already been rewrapped.
13061 let selection_range = start_row..end_row;
13062 if rewrapped_row_ranges
13063 .iter()
13064 .any(|range| range.overlaps(&selection_range))
13065 {
13066 continue;
13067 }
13068
13069 let tab_size = language_settings.tab_size;
13070
13071 let (line_prefix, inside_comment) = match &comment_prefix {
13072 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13073 (Some(prefix.as_str()), true)
13074 }
13075 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13076 (Some(prefix.as_ref()), true)
13077 }
13078 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13079 start: _,
13080 end: _,
13081 prefix,
13082 tab_size,
13083 })) => {
13084 indent_size.len += tab_size;
13085 (Some(prefix.as_ref()), true)
13086 }
13087 None => (None, false),
13088 };
13089 let indent_prefix = indent_size.chars().collect::<String>();
13090 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13091
13092 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13093 RewrapBehavior::InComments => inside_comment,
13094 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13095 RewrapBehavior::Anywhere => true,
13096 };
13097
13098 let should_rewrap = options.override_language_settings
13099 || allow_rewrap_based_on_language
13100 || self.hard_wrap.is_some();
13101 if !should_rewrap {
13102 continue;
13103 }
13104
13105 if from_empty_selection {
13106 'expand_upwards: while start_row > 0 {
13107 let prev_row = start_row - 1;
13108 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13109 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13110 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13111 {
13112 start_row = prev_row;
13113 } else {
13114 break 'expand_upwards;
13115 }
13116 }
13117
13118 'expand_downwards: while end_row < buffer.max_point().row {
13119 let next_row = end_row + 1;
13120 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13121 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13122 && !buffer.is_line_blank(MultiBufferRow(next_row))
13123 {
13124 end_row = next_row;
13125 } else {
13126 break 'expand_downwards;
13127 }
13128 }
13129 }
13130
13131 let start = Point::new(start_row, 0);
13132 let start_offset = ToOffset::to_offset(&start, &buffer);
13133 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13134 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13135 let mut first_line_delimiter = None;
13136 let mut last_line_delimiter = None;
13137 let Some(lines_without_prefixes) = selection_text
13138 .lines()
13139 .enumerate()
13140 .map(|(ix, line)| {
13141 let line_trimmed = line.trim_start();
13142 if rewrap_prefix.is_some() && ix > 0 {
13143 Ok(line_trimmed)
13144 } else if let Some(
13145 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13146 start,
13147 prefix,
13148 end,
13149 tab_size,
13150 })
13151 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13152 start,
13153 prefix,
13154 end,
13155 tab_size,
13156 }),
13157 ) = &comment_prefix
13158 {
13159 let line_trimmed = line_trimmed
13160 .strip_prefix(start.as_ref())
13161 .map(|s| {
13162 let mut indent_size = indent_size;
13163 indent_size.len -= tab_size;
13164 let indent_prefix: String = indent_size.chars().collect();
13165 first_line_delimiter = Some((indent_prefix, start));
13166 s.trim_start()
13167 })
13168 .unwrap_or(line_trimmed);
13169 let line_trimmed = line_trimmed
13170 .strip_suffix(end.as_ref())
13171 .map(|s| {
13172 last_line_delimiter = Some(end);
13173 s.trim_end()
13174 })
13175 .unwrap_or(line_trimmed);
13176 let line_trimmed = line_trimmed
13177 .strip_prefix(prefix.as_ref())
13178 .unwrap_or(line_trimmed);
13179 Ok(line_trimmed)
13180 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13181 line_trimmed.strip_prefix(prefix).with_context(|| {
13182 format!("line did not start with prefix {prefix:?}: {line:?}")
13183 })
13184 } else {
13185 line_trimmed
13186 .strip_prefix(&line_prefix.trim_start())
13187 .with_context(|| {
13188 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13189 })
13190 }
13191 })
13192 .collect::<Result<Vec<_>, _>>()
13193 .log_err()
13194 else {
13195 continue;
13196 };
13197
13198 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13199 buffer
13200 .language_settings_at(Point::new(start_row, 0), cx)
13201 .preferred_line_length as usize
13202 });
13203
13204 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13205 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13206 } else {
13207 line_prefix.clone()
13208 };
13209
13210 let wrapped_text = {
13211 let mut wrapped_text = wrap_with_prefix(
13212 line_prefix,
13213 subsequent_lines_prefix,
13214 lines_without_prefixes.join("\n"),
13215 wrap_column,
13216 tab_size,
13217 options.preserve_existing_whitespace,
13218 );
13219
13220 if let Some((indent, delimiter)) = first_line_delimiter {
13221 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13222 }
13223 if let Some(last_line) = last_line_delimiter {
13224 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13225 }
13226
13227 wrapped_text
13228 };
13229
13230 // TODO: should always use char-based diff while still supporting cursor behavior that
13231 // matches vim.
13232 let mut diff_options = DiffOptions::default();
13233 if options.override_language_settings {
13234 diff_options.max_word_diff_len = 0;
13235 diff_options.max_word_diff_line_count = 0;
13236 } else {
13237 diff_options.max_word_diff_len = usize::MAX;
13238 diff_options.max_word_diff_line_count = usize::MAX;
13239 }
13240
13241 for (old_range, new_text) in
13242 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13243 {
13244 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13245 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13246 edits.push((edit_start..edit_end, new_text));
13247 }
13248
13249 rewrapped_row_ranges.push(start_row..=end_row);
13250 }
13251
13252 self.buffer
13253 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13254 }
13255
13256 pub fn cut_common(
13257 &mut self,
13258 cut_no_selection_line: bool,
13259 window: &mut Window,
13260 cx: &mut Context<Self>,
13261 ) -> ClipboardItem {
13262 let mut text = String::new();
13263 let buffer = self.buffer.read(cx).snapshot(cx);
13264 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13265 let mut clipboard_selections = Vec::with_capacity(selections.len());
13266 {
13267 let max_point = buffer.max_point();
13268 let mut is_first = true;
13269 let mut prev_selection_was_entire_line = false;
13270 for selection in &mut selections {
13271 let is_entire_line =
13272 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13273 if is_entire_line {
13274 selection.start = Point::new(selection.start.row, 0);
13275 if !selection.is_empty() && selection.end.column == 0 {
13276 selection.end = cmp::min(max_point, selection.end);
13277 } else {
13278 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13279 }
13280 selection.goal = SelectionGoal::None;
13281 }
13282 if is_first {
13283 is_first = false;
13284 } else if !prev_selection_was_entire_line {
13285 text += "\n";
13286 }
13287 prev_selection_was_entire_line = is_entire_line;
13288 let mut len = 0;
13289 for chunk in buffer.text_for_range(selection.start..selection.end) {
13290 text.push_str(chunk);
13291 len += chunk.len();
13292 }
13293
13294 clipboard_selections.push(ClipboardSelection::for_buffer(
13295 len,
13296 is_entire_line,
13297 selection.range(),
13298 &buffer,
13299 self.project.as_ref(),
13300 cx,
13301 ));
13302 }
13303 }
13304
13305 self.transact(window, cx, |this, window, cx| {
13306 this.change_selections(Default::default(), window, cx, |s| {
13307 s.select(selections);
13308 });
13309 this.insert("", window, cx);
13310 });
13311 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13312 }
13313
13314 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13316 let item = self.cut_common(true, window, cx);
13317 cx.write_to_clipboard(item);
13318 }
13319
13320 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13322 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13323 s.move_with(|snapshot, sel| {
13324 if sel.is_empty() {
13325 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13326 }
13327 if sel.is_empty() {
13328 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13329 }
13330 });
13331 });
13332 let item = self.cut_common(false, window, cx);
13333 cx.set_global(KillRing(item))
13334 }
13335
13336 pub fn kill_ring_yank(
13337 &mut self,
13338 _: &KillRingYank,
13339 window: &mut Window,
13340 cx: &mut Context<Self>,
13341 ) {
13342 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13343 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13344 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13345 (kill_ring.text().to_string(), kill_ring.metadata_json())
13346 } else {
13347 return;
13348 }
13349 } else {
13350 return;
13351 };
13352 self.do_paste(&text, metadata, false, window, cx);
13353 }
13354
13355 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13356 self.do_copy(true, cx);
13357 }
13358
13359 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13360 self.do_copy(false, cx);
13361 }
13362
13363 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13364 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13365 let buffer = self.buffer.read(cx).read(cx);
13366 let mut text = String::new();
13367
13368 let mut clipboard_selections = Vec::with_capacity(selections.len());
13369 {
13370 let max_point = buffer.max_point();
13371 let mut is_first = true;
13372 let mut prev_selection_was_entire_line = false;
13373 for selection in &selections {
13374 let mut start = selection.start;
13375 let mut end = selection.end;
13376 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13377 let mut add_trailing_newline = false;
13378 if is_entire_line {
13379 start = Point::new(start.row, 0);
13380 let next_line_start = Point::new(end.row + 1, 0);
13381 if next_line_start <= max_point {
13382 end = next_line_start;
13383 } else {
13384 // We're on the last line without a trailing newline.
13385 // Copy to the end of the line and add a newline afterwards.
13386 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13387 add_trailing_newline = true;
13388 }
13389 }
13390
13391 let mut trimmed_selections = Vec::new();
13392 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13393 let row = MultiBufferRow(start.row);
13394 let first_indent = buffer.indent_size_for_line(row);
13395 if first_indent.len == 0 || start.column > first_indent.len {
13396 trimmed_selections.push(start..end);
13397 } else {
13398 trimmed_selections.push(
13399 Point::new(row.0, first_indent.len)
13400 ..Point::new(row.0, buffer.line_len(row)),
13401 );
13402 for row in start.row + 1..=end.row {
13403 let mut line_len = buffer.line_len(MultiBufferRow(row));
13404 if row == end.row {
13405 line_len = end.column;
13406 }
13407 if line_len == 0 {
13408 trimmed_selections
13409 .push(Point::new(row, 0)..Point::new(row, line_len));
13410 continue;
13411 }
13412 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13413 if row_indent_size.len >= first_indent.len {
13414 trimmed_selections.push(
13415 Point::new(row, first_indent.len)..Point::new(row, line_len),
13416 );
13417 } else {
13418 trimmed_selections.clear();
13419 trimmed_selections.push(start..end);
13420 break;
13421 }
13422 }
13423 }
13424 } else {
13425 trimmed_selections.push(start..end);
13426 }
13427
13428 let is_multiline_trim = trimmed_selections.len() > 1;
13429 for trimmed_range in trimmed_selections {
13430 if is_first {
13431 is_first = false;
13432 } else if is_multiline_trim || !prev_selection_was_entire_line {
13433 text += "\n";
13434 }
13435 prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13436 let mut len = 0;
13437 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13438 text.push_str(chunk);
13439 len += chunk.len();
13440 }
13441 if add_trailing_newline {
13442 text.push('\n');
13443 len += 1;
13444 }
13445 clipboard_selections.push(ClipboardSelection::for_buffer(
13446 len,
13447 is_entire_line,
13448 trimmed_range,
13449 &buffer,
13450 self.project.as_ref(),
13451 cx,
13452 ));
13453 }
13454 }
13455 }
13456
13457 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13458 text,
13459 clipboard_selections,
13460 ));
13461 }
13462
13463 pub fn do_paste(
13464 &mut self,
13465 text: &String,
13466 clipboard_selections: Option<Vec<ClipboardSelection>>,
13467 handle_entire_lines: bool,
13468 window: &mut Window,
13469 cx: &mut Context<Self>,
13470 ) {
13471 if self.read_only(cx) {
13472 return;
13473 }
13474
13475 let clipboard_text = Cow::Borrowed(text.as_str());
13476
13477 self.transact(window, cx, |this, window, cx| {
13478 let had_active_edit_prediction = this.has_active_edit_prediction();
13479 let display_map = this.display_snapshot(cx);
13480 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13481 let cursor_offset = this
13482 .selections
13483 .last::<MultiBufferOffset>(&display_map)
13484 .head();
13485
13486 if let Some(mut clipboard_selections) = clipboard_selections {
13487 let all_selections_were_entire_line =
13488 clipboard_selections.iter().all(|s| s.is_entire_line);
13489 let first_selection_indent_column =
13490 clipboard_selections.first().map(|s| s.first_line_indent);
13491 if clipboard_selections.len() != old_selections.len() {
13492 clipboard_selections.drain(..);
13493 }
13494 let mut auto_indent_on_paste = true;
13495
13496 this.buffer.update(cx, |buffer, cx| {
13497 let snapshot = buffer.read(cx);
13498 auto_indent_on_paste = snapshot
13499 .language_settings_at(cursor_offset, cx)
13500 .auto_indent_on_paste;
13501
13502 let mut start_offset = 0;
13503 let mut edits = Vec::new();
13504 let mut original_indent_columns = Vec::new();
13505 for (ix, selection) in old_selections.iter().enumerate() {
13506 let to_insert;
13507 let entire_line;
13508 let original_indent_column;
13509 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13510 let end_offset = start_offset + clipboard_selection.len;
13511 to_insert = &clipboard_text[start_offset..end_offset];
13512 entire_line = clipboard_selection.is_entire_line;
13513 start_offset = if entire_line {
13514 end_offset
13515 } else {
13516 end_offset + 1
13517 };
13518 original_indent_column = Some(clipboard_selection.first_line_indent);
13519 } else {
13520 to_insert = &*clipboard_text;
13521 entire_line = all_selections_were_entire_line;
13522 original_indent_column = first_selection_indent_column
13523 }
13524
13525 let (range, to_insert) =
13526 if selection.is_empty() && handle_entire_lines && entire_line {
13527 // If the corresponding selection was empty when this slice of the
13528 // clipboard text was written, then the entire line containing the
13529 // selection was copied. If this selection is also currently empty,
13530 // then paste the line before the current line of the buffer.
13531 let column = selection.start.to_point(&snapshot).column as usize;
13532 let line_start = selection.start - column;
13533 (line_start..line_start, Cow::Borrowed(to_insert))
13534 } else {
13535 let language = snapshot.language_at(selection.head());
13536 let range = selection.range();
13537 if let Some(language) = language
13538 && language.name() == "Markdown".into()
13539 {
13540 edit_for_markdown_paste(
13541 &snapshot,
13542 range,
13543 to_insert,
13544 url::Url::parse(to_insert).ok(),
13545 )
13546 } else {
13547 (range, Cow::Borrowed(to_insert))
13548 }
13549 };
13550
13551 edits.push((range, to_insert));
13552 original_indent_columns.push(original_indent_column);
13553 }
13554 drop(snapshot);
13555
13556 buffer.edit(
13557 edits,
13558 if auto_indent_on_paste {
13559 Some(AutoindentMode::Block {
13560 original_indent_columns,
13561 })
13562 } else {
13563 None
13564 },
13565 cx,
13566 );
13567 });
13568
13569 let selections = this
13570 .selections
13571 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13572 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13573 } else {
13574 let url = url::Url::parse(&clipboard_text).ok();
13575
13576 let auto_indent_mode = if !clipboard_text.is_empty() {
13577 Some(AutoindentMode::Block {
13578 original_indent_columns: Vec::new(),
13579 })
13580 } else {
13581 None
13582 };
13583
13584 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13585 let snapshot = buffer.snapshot(cx);
13586
13587 let anchors = old_selections
13588 .iter()
13589 .map(|s| {
13590 let anchor = snapshot.anchor_after(s.head());
13591 s.map(|_| anchor)
13592 })
13593 .collect::<Vec<_>>();
13594
13595 let mut edits = Vec::new();
13596
13597 for selection in old_selections.iter() {
13598 let language = snapshot.language_at(selection.head());
13599 let range = selection.range();
13600
13601 let (edit_range, edit_text) = if let Some(language) = language
13602 && language.name() == "Markdown".into()
13603 {
13604 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13605 } else {
13606 (range, clipboard_text.clone())
13607 };
13608
13609 edits.push((edit_range, edit_text));
13610 }
13611
13612 drop(snapshot);
13613 buffer.edit(edits, auto_indent_mode, cx);
13614
13615 anchors
13616 });
13617
13618 this.change_selections(Default::default(), window, cx, |s| {
13619 s.select_anchors(selection_anchors);
13620 });
13621 }
13622
13623 // 🤔 | .. | show_in_menu |
13624 // | .. | true true
13625 // | had_edit_prediction | false true
13626
13627 let trigger_in_words =
13628 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13629
13630 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13631 });
13632 }
13633
13634 pub fn diff_clipboard_with_selection(
13635 &mut self,
13636 _: &DiffClipboardWithSelection,
13637 window: &mut Window,
13638 cx: &mut Context<Self>,
13639 ) {
13640 let selections = self
13641 .selections
13642 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13643
13644 if selections.is_empty() {
13645 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13646 return;
13647 };
13648
13649 let clipboard_text = match cx.read_from_clipboard() {
13650 Some(item) => match item.entries().first() {
13651 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13652 _ => None,
13653 },
13654 None => None,
13655 };
13656
13657 let Some(clipboard_text) = clipboard_text else {
13658 log::warn!("Clipboard doesn't contain text.");
13659 return;
13660 };
13661
13662 window.dispatch_action(
13663 Box::new(DiffClipboardWithSelectionData {
13664 clipboard_text,
13665 editor: cx.entity(),
13666 }),
13667 cx,
13668 );
13669 }
13670
13671 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13673 if let Some(item) = cx.read_from_clipboard() {
13674 let entries = item.entries();
13675
13676 match entries.first() {
13677 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13678 // of all the pasted entries.
13679 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13680 .do_paste(
13681 clipboard_string.text(),
13682 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13683 true,
13684 window,
13685 cx,
13686 ),
13687 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13688 }
13689 }
13690 }
13691
13692 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13693 if self.read_only(cx) {
13694 return;
13695 }
13696
13697 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13698
13699 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13700 if let Some((selections, _)) =
13701 self.selection_history.transaction(transaction_id).cloned()
13702 {
13703 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13704 s.select_anchors(selections.to_vec());
13705 });
13706 } else {
13707 log::error!(
13708 "No entry in selection_history found for undo. \
13709 This may correspond to a bug where undo does not update the selection. \
13710 If this is occurring, please add details to \
13711 https://github.com/zed-industries/zed/issues/22692"
13712 );
13713 }
13714 self.request_autoscroll(Autoscroll::fit(), cx);
13715 self.unmark_text(window, cx);
13716 self.refresh_edit_prediction(true, false, window, cx);
13717 cx.emit(EditorEvent::Edited { transaction_id });
13718 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13719 }
13720 }
13721
13722 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13723 if self.read_only(cx) {
13724 return;
13725 }
13726
13727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13728
13729 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13730 if let Some((_, Some(selections))) =
13731 self.selection_history.transaction(transaction_id).cloned()
13732 {
13733 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13734 s.select_anchors(selections.to_vec());
13735 });
13736 } else {
13737 log::error!(
13738 "No entry in selection_history found for redo. \
13739 This may correspond to a bug where undo does not update the selection. \
13740 If this is occurring, please add details to \
13741 https://github.com/zed-industries/zed/issues/22692"
13742 );
13743 }
13744 self.request_autoscroll(Autoscroll::fit(), cx);
13745 self.unmark_text(window, cx);
13746 self.refresh_edit_prediction(true, false, window, cx);
13747 cx.emit(EditorEvent::Edited { transaction_id });
13748 }
13749 }
13750
13751 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13752 self.buffer
13753 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13754 }
13755
13756 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13757 self.buffer
13758 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13759 }
13760
13761 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13762 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13763 self.change_selections(Default::default(), window, cx, |s| {
13764 s.move_with(|map, selection| {
13765 let cursor = if selection.is_empty() {
13766 movement::left(map, selection.start)
13767 } else {
13768 selection.start
13769 };
13770 selection.collapse_to(cursor, SelectionGoal::None);
13771 });
13772 })
13773 }
13774
13775 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13776 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13777 self.change_selections(Default::default(), window, cx, |s| {
13778 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13779 })
13780 }
13781
13782 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13783 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13784 self.change_selections(Default::default(), window, cx, |s| {
13785 s.move_with(|map, selection| {
13786 let cursor = if selection.is_empty() {
13787 movement::right(map, selection.end)
13788 } else {
13789 selection.end
13790 };
13791 selection.collapse_to(cursor, SelectionGoal::None)
13792 });
13793 })
13794 }
13795
13796 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13798 self.change_selections(Default::default(), window, cx, |s| {
13799 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13800 });
13801 }
13802
13803 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13804 if self.take_rename(true, window, cx).is_some() {
13805 return;
13806 }
13807
13808 if self.mode.is_single_line() {
13809 cx.propagate();
13810 return;
13811 }
13812
13813 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13814
13815 let text_layout_details = &self.text_layout_details(window);
13816 let selection_count = self.selections.count();
13817 let first_selection = self.selections.first_anchor();
13818
13819 self.change_selections(Default::default(), window, cx, |s| {
13820 s.move_with(|map, selection| {
13821 if !selection.is_empty() {
13822 selection.goal = SelectionGoal::None;
13823 }
13824 let (cursor, goal) = movement::up(
13825 map,
13826 selection.start,
13827 selection.goal,
13828 false,
13829 text_layout_details,
13830 );
13831 selection.collapse_to(cursor, goal);
13832 });
13833 });
13834
13835 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13836 {
13837 cx.propagate();
13838 }
13839 }
13840
13841 pub fn move_up_by_lines(
13842 &mut self,
13843 action: &MoveUpByLines,
13844 window: &mut Window,
13845 cx: &mut Context<Self>,
13846 ) {
13847 if self.take_rename(true, window, cx).is_some() {
13848 return;
13849 }
13850
13851 if self.mode.is_single_line() {
13852 cx.propagate();
13853 return;
13854 }
13855
13856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13857
13858 let text_layout_details = &self.text_layout_details(window);
13859
13860 self.change_selections(Default::default(), window, cx, |s| {
13861 s.move_with(|map, selection| {
13862 if !selection.is_empty() {
13863 selection.goal = SelectionGoal::None;
13864 }
13865 let (cursor, goal) = movement::up_by_rows(
13866 map,
13867 selection.start,
13868 action.lines,
13869 selection.goal,
13870 false,
13871 text_layout_details,
13872 );
13873 selection.collapse_to(cursor, goal);
13874 });
13875 })
13876 }
13877
13878 pub fn move_down_by_lines(
13879 &mut self,
13880 action: &MoveDownByLines,
13881 window: &mut Window,
13882 cx: &mut Context<Self>,
13883 ) {
13884 if self.take_rename(true, window, cx).is_some() {
13885 return;
13886 }
13887
13888 if self.mode.is_single_line() {
13889 cx.propagate();
13890 return;
13891 }
13892
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894
13895 let text_layout_details = &self.text_layout_details(window);
13896
13897 self.change_selections(Default::default(), window, cx, |s| {
13898 s.move_with(|map, selection| {
13899 if !selection.is_empty() {
13900 selection.goal = SelectionGoal::None;
13901 }
13902 let (cursor, goal) = movement::down_by_rows(
13903 map,
13904 selection.start,
13905 action.lines,
13906 selection.goal,
13907 false,
13908 text_layout_details,
13909 );
13910 selection.collapse_to(cursor, goal);
13911 });
13912 })
13913 }
13914
13915 pub fn select_down_by_lines(
13916 &mut self,
13917 action: &SelectDownByLines,
13918 window: &mut Window,
13919 cx: &mut Context<Self>,
13920 ) {
13921 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13922 let text_layout_details = &self.text_layout_details(window);
13923 self.change_selections(Default::default(), window, cx, |s| {
13924 s.move_heads_with(|map, head, goal| {
13925 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13926 })
13927 })
13928 }
13929
13930 pub fn select_up_by_lines(
13931 &mut self,
13932 action: &SelectUpByLines,
13933 window: &mut Window,
13934 cx: &mut Context<Self>,
13935 ) {
13936 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13937 let text_layout_details = &self.text_layout_details(window);
13938 self.change_selections(Default::default(), window, cx, |s| {
13939 s.move_heads_with(|map, head, goal| {
13940 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13941 })
13942 })
13943 }
13944
13945 pub fn select_page_up(
13946 &mut self,
13947 _: &SelectPageUp,
13948 window: &mut Window,
13949 cx: &mut Context<Self>,
13950 ) {
13951 let Some(row_count) = self.visible_row_count() else {
13952 return;
13953 };
13954
13955 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13956
13957 let text_layout_details = &self.text_layout_details(window);
13958
13959 self.change_selections(Default::default(), window, cx, |s| {
13960 s.move_heads_with(|map, head, goal| {
13961 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13962 })
13963 })
13964 }
13965
13966 pub fn move_page_up(
13967 &mut self,
13968 action: &MovePageUp,
13969 window: &mut Window,
13970 cx: &mut Context<Self>,
13971 ) {
13972 if self.take_rename(true, window, cx).is_some() {
13973 return;
13974 }
13975
13976 if self
13977 .context_menu
13978 .borrow_mut()
13979 .as_mut()
13980 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13981 .unwrap_or(false)
13982 {
13983 return;
13984 }
13985
13986 if matches!(self.mode, EditorMode::SingleLine) {
13987 cx.propagate();
13988 return;
13989 }
13990
13991 let Some(row_count) = self.visible_row_count() else {
13992 return;
13993 };
13994
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996
13997 let effects = if action.center_cursor {
13998 SelectionEffects::scroll(Autoscroll::center())
13999 } else {
14000 SelectionEffects::default()
14001 };
14002
14003 let text_layout_details = &self.text_layout_details(window);
14004
14005 self.change_selections(effects, window, cx, |s| {
14006 s.move_with(|map, selection| {
14007 if !selection.is_empty() {
14008 selection.goal = SelectionGoal::None;
14009 }
14010 let (cursor, goal) = movement::up_by_rows(
14011 map,
14012 selection.end,
14013 row_count,
14014 selection.goal,
14015 false,
14016 text_layout_details,
14017 );
14018 selection.collapse_to(cursor, goal);
14019 });
14020 });
14021 }
14022
14023 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14025 let text_layout_details = &self.text_layout_details(window);
14026 self.change_selections(Default::default(), window, cx, |s| {
14027 s.move_heads_with(|map, head, goal| {
14028 movement::up(map, head, goal, false, text_layout_details)
14029 })
14030 })
14031 }
14032
14033 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14034 self.take_rename(true, window, cx);
14035
14036 if self.mode.is_single_line() {
14037 cx.propagate();
14038 return;
14039 }
14040
14041 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14042
14043 let text_layout_details = &self.text_layout_details(window);
14044 let selection_count = self.selections.count();
14045 let first_selection = self.selections.first_anchor();
14046
14047 self.change_selections(Default::default(), window, cx, |s| {
14048 s.move_with(|map, selection| {
14049 if !selection.is_empty() {
14050 selection.goal = SelectionGoal::None;
14051 }
14052 let (cursor, goal) = movement::down(
14053 map,
14054 selection.end,
14055 selection.goal,
14056 false,
14057 text_layout_details,
14058 );
14059 selection.collapse_to(cursor, goal);
14060 });
14061 });
14062
14063 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14064 {
14065 cx.propagate();
14066 }
14067 }
14068
14069 pub fn select_page_down(
14070 &mut self,
14071 _: &SelectPageDown,
14072 window: &mut Window,
14073 cx: &mut Context<Self>,
14074 ) {
14075 let Some(row_count) = self.visible_row_count() else {
14076 return;
14077 };
14078
14079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14080
14081 let text_layout_details = &self.text_layout_details(window);
14082
14083 self.change_selections(Default::default(), window, cx, |s| {
14084 s.move_heads_with(|map, head, goal| {
14085 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14086 })
14087 })
14088 }
14089
14090 pub fn move_page_down(
14091 &mut self,
14092 action: &MovePageDown,
14093 window: &mut Window,
14094 cx: &mut Context<Self>,
14095 ) {
14096 if self.take_rename(true, window, cx).is_some() {
14097 return;
14098 }
14099
14100 if self
14101 .context_menu
14102 .borrow_mut()
14103 .as_mut()
14104 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14105 .unwrap_or(false)
14106 {
14107 return;
14108 }
14109
14110 if matches!(self.mode, EditorMode::SingleLine) {
14111 cx.propagate();
14112 return;
14113 }
14114
14115 let Some(row_count) = self.visible_row_count() else {
14116 return;
14117 };
14118
14119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14120
14121 let effects = if action.center_cursor {
14122 SelectionEffects::scroll(Autoscroll::center())
14123 } else {
14124 SelectionEffects::default()
14125 };
14126
14127 let text_layout_details = &self.text_layout_details(window);
14128 self.change_selections(effects, window, cx, |s| {
14129 s.move_with(|map, selection| {
14130 if !selection.is_empty() {
14131 selection.goal = SelectionGoal::None;
14132 }
14133 let (cursor, goal) = movement::down_by_rows(
14134 map,
14135 selection.end,
14136 row_count,
14137 selection.goal,
14138 false,
14139 text_layout_details,
14140 );
14141 selection.collapse_to(cursor, goal);
14142 });
14143 });
14144 }
14145
14146 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14148 let text_layout_details = &self.text_layout_details(window);
14149 self.change_selections(Default::default(), window, cx, |s| {
14150 s.move_heads_with(|map, head, goal| {
14151 movement::down(map, head, goal, false, text_layout_details)
14152 })
14153 });
14154 }
14155
14156 pub fn context_menu_first(
14157 &mut self,
14158 _: &ContextMenuFirst,
14159 window: &mut Window,
14160 cx: &mut Context<Self>,
14161 ) {
14162 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14163 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14164 }
14165 }
14166
14167 pub fn context_menu_prev(
14168 &mut self,
14169 _: &ContextMenuPrevious,
14170 window: &mut Window,
14171 cx: &mut Context<Self>,
14172 ) {
14173 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14174 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14175 }
14176 }
14177
14178 pub fn context_menu_next(
14179 &mut self,
14180 _: &ContextMenuNext,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) {
14184 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14185 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14186 }
14187 }
14188
14189 pub fn context_menu_last(
14190 &mut self,
14191 _: &ContextMenuLast,
14192 window: &mut Window,
14193 cx: &mut Context<Self>,
14194 ) {
14195 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14196 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14197 }
14198 }
14199
14200 pub fn signature_help_prev(
14201 &mut self,
14202 _: &SignatureHelpPrevious,
14203 _: &mut Window,
14204 cx: &mut Context<Self>,
14205 ) {
14206 if let Some(popover) = self.signature_help_state.popover_mut() {
14207 if popover.current_signature == 0 {
14208 popover.current_signature = popover.signatures.len() - 1;
14209 } else {
14210 popover.current_signature -= 1;
14211 }
14212 cx.notify();
14213 }
14214 }
14215
14216 pub fn signature_help_next(
14217 &mut self,
14218 _: &SignatureHelpNext,
14219 _: &mut Window,
14220 cx: &mut Context<Self>,
14221 ) {
14222 if let Some(popover) = self.signature_help_state.popover_mut() {
14223 if popover.current_signature + 1 == popover.signatures.len() {
14224 popover.current_signature = 0;
14225 } else {
14226 popover.current_signature += 1;
14227 }
14228 cx.notify();
14229 }
14230 }
14231
14232 pub fn move_to_previous_word_start(
14233 &mut self,
14234 _: &MoveToPreviousWordStart,
14235 window: &mut Window,
14236 cx: &mut Context<Self>,
14237 ) {
14238 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14239 self.change_selections(Default::default(), window, cx, |s| {
14240 s.move_cursors_with(|map, head, _| {
14241 (
14242 movement::previous_word_start(map, head),
14243 SelectionGoal::None,
14244 )
14245 });
14246 })
14247 }
14248
14249 pub fn move_to_previous_subword_start(
14250 &mut self,
14251 _: &MoveToPreviousSubwordStart,
14252 window: &mut Window,
14253 cx: &mut Context<Self>,
14254 ) {
14255 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14256 self.change_selections(Default::default(), window, cx, |s| {
14257 s.move_cursors_with(|map, head, _| {
14258 (
14259 movement::previous_subword_start(map, head),
14260 SelectionGoal::None,
14261 )
14262 });
14263 })
14264 }
14265
14266 pub fn select_to_previous_word_start(
14267 &mut self,
14268 _: &SelectToPreviousWordStart,
14269 window: &mut Window,
14270 cx: &mut Context<Self>,
14271 ) {
14272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14273 self.change_selections(Default::default(), window, cx, |s| {
14274 s.move_heads_with(|map, head, _| {
14275 (
14276 movement::previous_word_start(map, head),
14277 SelectionGoal::None,
14278 )
14279 });
14280 })
14281 }
14282
14283 pub fn select_to_previous_subword_start(
14284 &mut self,
14285 _: &SelectToPreviousSubwordStart,
14286 window: &mut Window,
14287 cx: &mut Context<Self>,
14288 ) {
14289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14290 self.change_selections(Default::default(), window, cx, |s| {
14291 s.move_heads_with(|map, head, _| {
14292 (
14293 movement::previous_subword_start(map, head),
14294 SelectionGoal::None,
14295 )
14296 });
14297 })
14298 }
14299
14300 pub fn delete_to_previous_word_start(
14301 &mut self,
14302 action: &DeleteToPreviousWordStart,
14303 window: &mut Window,
14304 cx: &mut Context<Self>,
14305 ) {
14306 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14307 self.transact(window, cx, |this, window, cx| {
14308 this.select_autoclose_pair(window, cx);
14309 this.change_selections(Default::default(), window, cx, |s| {
14310 s.move_with(|map, selection| {
14311 if selection.is_empty() {
14312 let mut cursor = if action.ignore_newlines {
14313 movement::previous_word_start(map, selection.head())
14314 } else {
14315 movement::previous_word_start_or_newline(map, selection.head())
14316 };
14317 cursor = movement::adjust_greedy_deletion(
14318 map,
14319 selection.head(),
14320 cursor,
14321 action.ignore_brackets,
14322 );
14323 selection.set_head(cursor, SelectionGoal::None);
14324 }
14325 });
14326 });
14327 this.insert("", window, cx);
14328 });
14329 }
14330
14331 pub fn delete_to_previous_subword_start(
14332 &mut self,
14333 action: &DeleteToPreviousSubwordStart,
14334 window: &mut Window,
14335 cx: &mut Context<Self>,
14336 ) {
14337 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14338 self.transact(window, cx, |this, window, cx| {
14339 this.select_autoclose_pair(window, cx);
14340 this.change_selections(Default::default(), window, cx, |s| {
14341 s.move_with(|map, selection| {
14342 if selection.is_empty() {
14343 let mut cursor = if action.ignore_newlines {
14344 movement::previous_subword_start(map, selection.head())
14345 } else {
14346 movement::previous_subword_start_or_newline(map, selection.head())
14347 };
14348 cursor = movement::adjust_greedy_deletion(
14349 map,
14350 selection.head(),
14351 cursor,
14352 action.ignore_brackets,
14353 );
14354 selection.set_head(cursor, SelectionGoal::None);
14355 }
14356 });
14357 });
14358 this.insert("", window, cx);
14359 });
14360 }
14361
14362 pub fn move_to_next_word_end(
14363 &mut self,
14364 _: &MoveToNextWordEnd,
14365 window: &mut Window,
14366 cx: &mut Context<Self>,
14367 ) {
14368 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14369 self.change_selections(Default::default(), window, cx, |s| {
14370 s.move_cursors_with(|map, head, _| {
14371 (movement::next_word_end(map, head), SelectionGoal::None)
14372 });
14373 })
14374 }
14375
14376 pub fn move_to_next_subword_end(
14377 &mut self,
14378 _: &MoveToNextSubwordEnd,
14379 window: &mut Window,
14380 cx: &mut Context<Self>,
14381 ) {
14382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14383 self.change_selections(Default::default(), window, cx, |s| {
14384 s.move_cursors_with(|map, head, _| {
14385 (movement::next_subword_end(map, head), SelectionGoal::None)
14386 });
14387 })
14388 }
14389
14390 pub fn select_to_next_word_end(
14391 &mut self,
14392 _: &SelectToNextWordEnd,
14393 window: &mut Window,
14394 cx: &mut Context<Self>,
14395 ) {
14396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14397 self.change_selections(Default::default(), window, cx, |s| {
14398 s.move_heads_with(|map, head, _| {
14399 (movement::next_word_end(map, head), SelectionGoal::None)
14400 });
14401 })
14402 }
14403
14404 pub fn select_to_next_subword_end(
14405 &mut self,
14406 _: &SelectToNextSubwordEnd,
14407 window: &mut Window,
14408 cx: &mut Context<Self>,
14409 ) {
14410 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14411 self.change_selections(Default::default(), window, cx, |s| {
14412 s.move_heads_with(|map, head, _| {
14413 (movement::next_subword_end(map, head), SelectionGoal::None)
14414 });
14415 })
14416 }
14417
14418 pub fn delete_to_next_word_end(
14419 &mut self,
14420 action: &DeleteToNextWordEnd,
14421 window: &mut Window,
14422 cx: &mut Context<Self>,
14423 ) {
14424 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14425 self.transact(window, cx, |this, window, cx| {
14426 this.change_selections(Default::default(), window, cx, |s| {
14427 s.move_with(|map, selection| {
14428 if selection.is_empty() {
14429 let mut cursor = if action.ignore_newlines {
14430 movement::next_word_end(map, selection.head())
14431 } else {
14432 movement::next_word_end_or_newline(map, selection.head())
14433 };
14434 cursor = movement::adjust_greedy_deletion(
14435 map,
14436 selection.head(),
14437 cursor,
14438 action.ignore_brackets,
14439 );
14440 selection.set_head(cursor, SelectionGoal::None);
14441 }
14442 });
14443 });
14444 this.insert("", window, cx);
14445 });
14446 }
14447
14448 pub fn delete_to_next_subword_end(
14449 &mut self,
14450 action: &DeleteToNextSubwordEnd,
14451 window: &mut Window,
14452 cx: &mut Context<Self>,
14453 ) {
14454 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14455 self.transact(window, cx, |this, window, cx| {
14456 this.change_selections(Default::default(), window, cx, |s| {
14457 s.move_with(|map, selection| {
14458 if selection.is_empty() {
14459 let mut cursor = if action.ignore_newlines {
14460 movement::next_subword_end(map, selection.head())
14461 } else {
14462 movement::next_subword_end_or_newline(map, selection.head())
14463 };
14464 cursor = movement::adjust_greedy_deletion(
14465 map,
14466 selection.head(),
14467 cursor,
14468 action.ignore_brackets,
14469 );
14470 selection.set_head(cursor, SelectionGoal::None);
14471 }
14472 });
14473 });
14474 this.insert("", window, cx);
14475 });
14476 }
14477
14478 pub fn move_to_beginning_of_line(
14479 &mut self,
14480 action: &MoveToBeginningOfLine,
14481 window: &mut Window,
14482 cx: &mut Context<Self>,
14483 ) {
14484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14485 self.change_selections(Default::default(), window, cx, |s| {
14486 s.move_cursors_with(|map, head, _| {
14487 (
14488 movement::indented_line_beginning(
14489 map,
14490 head,
14491 action.stop_at_soft_wraps,
14492 action.stop_at_indent,
14493 ),
14494 SelectionGoal::None,
14495 )
14496 });
14497 })
14498 }
14499
14500 pub fn select_to_beginning_of_line(
14501 &mut self,
14502 action: &SelectToBeginningOfLine,
14503 window: &mut Window,
14504 cx: &mut Context<Self>,
14505 ) {
14506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14507 self.change_selections(Default::default(), window, cx, |s| {
14508 s.move_heads_with(|map, head, _| {
14509 (
14510 movement::indented_line_beginning(
14511 map,
14512 head,
14513 action.stop_at_soft_wraps,
14514 action.stop_at_indent,
14515 ),
14516 SelectionGoal::None,
14517 )
14518 });
14519 });
14520 }
14521
14522 pub fn delete_to_beginning_of_line(
14523 &mut self,
14524 action: &DeleteToBeginningOfLine,
14525 window: &mut Window,
14526 cx: &mut Context<Self>,
14527 ) {
14528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14529 self.transact(window, cx, |this, window, cx| {
14530 this.change_selections(Default::default(), window, cx, |s| {
14531 s.move_with(|_, selection| {
14532 selection.reversed = true;
14533 });
14534 });
14535
14536 this.select_to_beginning_of_line(
14537 &SelectToBeginningOfLine {
14538 stop_at_soft_wraps: false,
14539 stop_at_indent: action.stop_at_indent,
14540 },
14541 window,
14542 cx,
14543 );
14544 this.backspace(&Backspace, window, cx);
14545 });
14546 }
14547
14548 pub fn move_to_end_of_line(
14549 &mut self,
14550 action: &MoveToEndOfLine,
14551 window: &mut Window,
14552 cx: &mut Context<Self>,
14553 ) {
14554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14555 self.change_selections(Default::default(), window, cx, |s| {
14556 s.move_cursors_with(|map, head, _| {
14557 (
14558 movement::line_end(map, head, action.stop_at_soft_wraps),
14559 SelectionGoal::None,
14560 )
14561 });
14562 })
14563 }
14564
14565 pub fn select_to_end_of_line(
14566 &mut self,
14567 action: &SelectToEndOfLine,
14568 window: &mut Window,
14569 cx: &mut Context<Self>,
14570 ) {
14571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14572 self.change_selections(Default::default(), window, cx, |s| {
14573 s.move_heads_with(|map, head, _| {
14574 (
14575 movement::line_end(map, head, action.stop_at_soft_wraps),
14576 SelectionGoal::None,
14577 )
14578 });
14579 })
14580 }
14581
14582 pub fn delete_to_end_of_line(
14583 &mut self,
14584 _: &DeleteToEndOfLine,
14585 window: &mut Window,
14586 cx: &mut Context<Self>,
14587 ) {
14588 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14589 self.transact(window, cx, |this, window, cx| {
14590 this.select_to_end_of_line(
14591 &SelectToEndOfLine {
14592 stop_at_soft_wraps: false,
14593 },
14594 window,
14595 cx,
14596 );
14597 this.delete(&Delete, window, cx);
14598 });
14599 }
14600
14601 pub fn cut_to_end_of_line(
14602 &mut self,
14603 action: &CutToEndOfLine,
14604 window: &mut Window,
14605 cx: &mut Context<Self>,
14606 ) {
14607 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14608 self.transact(window, cx, |this, window, cx| {
14609 this.select_to_end_of_line(
14610 &SelectToEndOfLine {
14611 stop_at_soft_wraps: false,
14612 },
14613 window,
14614 cx,
14615 );
14616 if !action.stop_at_newlines {
14617 this.change_selections(Default::default(), window, cx, |s| {
14618 s.move_with(|_, sel| {
14619 if sel.is_empty() {
14620 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14621 }
14622 });
14623 });
14624 }
14625 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14626 let item = this.cut_common(false, window, cx);
14627 cx.write_to_clipboard(item);
14628 });
14629 }
14630
14631 pub fn move_to_start_of_paragraph(
14632 &mut self,
14633 _: &MoveToStartOfParagraph,
14634 window: &mut Window,
14635 cx: &mut Context<Self>,
14636 ) {
14637 if matches!(self.mode, EditorMode::SingleLine) {
14638 cx.propagate();
14639 return;
14640 }
14641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14642 self.change_selections(Default::default(), window, cx, |s| {
14643 s.move_with(|map, selection| {
14644 selection.collapse_to(
14645 movement::start_of_paragraph(map, selection.head(), 1),
14646 SelectionGoal::None,
14647 )
14648 });
14649 })
14650 }
14651
14652 pub fn move_to_end_of_paragraph(
14653 &mut self,
14654 _: &MoveToEndOfParagraph,
14655 window: &mut Window,
14656 cx: &mut Context<Self>,
14657 ) {
14658 if matches!(self.mode, EditorMode::SingleLine) {
14659 cx.propagate();
14660 return;
14661 }
14662 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14663 self.change_selections(Default::default(), window, cx, |s| {
14664 s.move_with(|map, selection| {
14665 selection.collapse_to(
14666 movement::end_of_paragraph(map, selection.head(), 1),
14667 SelectionGoal::None,
14668 )
14669 });
14670 })
14671 }
14672
14673 pub fn select_to_start_of_paragraph(
14674 &mut self,
14675 _: &SelectToStartOfParagraph,
14676 window: &mut Window,
14677 cx: &mut Context<Self>,
14678 ) {
14679 if matches!(self.mode, EditorMode::SingleLine) {
14680 cx.propagate();
14681 return;
14682 }
14683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14684 self.change_selections(Default::default(), window, cx, |s| {
14685 s.move_heads_with(|map, head, _| {
14686 (
14687 movement::start_of_paragraph(map, head, 1),
14688 SelectionGoal::None,
14689 )
14690 });
14691 })
14692 }
14693
14694 pub fn select_to_end_of_paragraph(
14695 &mut self,
14696 _: &SelectToEndOfParagraph,
14697 window: &mut Window,
14698 cx: &mut Context<Self>,
14699 ) {
14700 if matches!(self.mode, EditorMode::SingleLine) {
14701 cx.propagate();
14702 return;
14703 }
14704 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14705 self.change_selections(Default::default(), window, cx, |s| {
14706 s.move_heads_with(|map, head, _| {
14707 (
14708 movement::end_of_paragraph(map, head, 1),
14709 SelectionGoal::None,
14710 )
14711 });
14712 })
14713 }
14714
14715 pub fn move_to_start_of_excerpt(
14716 &mut self,
14717 _: &MoveToStartOfExcerpt,
14718 window: &mut Window,
14719 cx: &mut Context<Self>,
14720 ) {
14721 if matches!(self.mode, EditorMode::SingleLine) {
14722 cx.propagate();
14723 return;
14724 }
14725 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14726 self.change_selections(Default::default(), window, cx, |s| {
14727 s.move_with(|map, selection| {
14728 selection.collapse_to(
14729 movement::start_of_excerpt(
14730 map,
14731 selection.head(),
14732 workspace::searchable::Direction::Prev,
14733 ),
14734 SelectionGoal::None,
14735 )
14736 });
14737 })
14738 }
14739
14740 pub fn move_to_start_of_next_excerpt(
14741 &mut self,
14742 _: &MoveToStartOfNextExcerpt,
14743 window: &mut Window,
14744 cx: &mut Context<Self>,
14745 ) {
14746 if matches!(self.mode, EditorMode::SingleLine) {
14747 cx.propagate();
14748 return;
14749 }
14750
14751 self.change_selections(Default::default(), window, cx, |s| {
14752 s.move_with(|map, selection| {
14753 selection.collapse_to(
14754 movement::start_of_excerpt(
14755 map,
14756 selection.head(),
14757 workspace::searchable::Direction::Next,
14758 ),
14759 SelectionGoal::None,
14760 )
14761 });
14762 })
14763 }
14764
14765 pub fn move_to_end_of_excerpt(
14766 &mut self,
14767 _: &MoveToEndOfExcerpt,
14768 window: &mut Window,
14769 cx: &mut Context<Self>,
14770 ) {
14771 if matches!(self.mode, EditorMode::SingleLine) {
14772 cx.propagate();
14773 return;
14774 }
14775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14776 self.change_selections(Default::default(), window, cx, |s| {
14777 s.move_with(|map, selection| {
14778 selection.collapse_to(
14779 movement::end_of_excerpt(
14780 map,
14781 selection.head(),
14782 workspace::searchable::Direction::Next,
14783 ),
14784 SelectionGoal::None,
14785 )
14786 });
14787 })
14788 }
14789
14790 pub fn move_to_end_of_previous_excerpt(
14791 &mut self,
14792 _: &MoveToEndOfPreviousExcerpt,
14793 window: &mut Window,
14794 cx: &mut Context<Self>,
14795 ) {
14796 if matches!(self.mode, EditorMode::SingleLine) {
14797 cx.propagate();
14798 return;
14799 }
14800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14801 self.change_selections(Default::default(), window, cx, |s| {
14802 s.move_with(|map, selection| {
14803 selection.collapse_to(
14804 movement::end_of_excerpt(
14805 map,
14806 selection.head(),
14807 workspace::searchable::Direction::Prev,
14808 ),
14809 SelectionGoal::None,
14810 )
14811 });
14812 })
14813 }
14814
14815 pub fn select_to_start_of_excerpt(
14816 &mut self,
14817 _: &SelectToStartOfExcerpt,
14818 window: &mut Window,
14819 cx: &mut Context<Self>,
14820 ) {
14821 if matches!(self.mode, EditorMode::SingleLine) {
14822 cx.propagate();
14823 return;
14824 }
14825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14826 self.change_selections(Default::default(), window, cx, |s| {
14827 s.move_heads_with(|map, head, _| {
14828 (
14829 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14830 SelectionGoal::None,
14831 )
14832 });
14833 })
14834 }
14835
14836 pub fn select_to_start_of_next_excerpt(
14837 &mut self,
14838 _: &SelectToStartOfNextExcerpt,
14839 window: &mut Window,
14840 cx: &mut Context<Self>,
14841 ) {
14842 if matches!(self.mode, EditorMode::SingleLine) {
14843 cx.propagate();
14844 return;
14845 }
14846 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14847 self.change_selections(Default::default(), window, cx, |s| {
14848 s.move_heads_with(|map, head, _| {
14849 (
14850 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14851 SelectionGoal::None,
14852 )
14853 });
14854 })
14855 }
14856
14857 pub fn select_to_end_of_excerpt(
14858 &mut self,
14859 _: &SelectToEndOfExcerpt,
14860 window: &mut Window,
14861 cx: &mut Context<Self>,
14862 ) {
14863 if matches!(self.mode, EditorMode::SingleLine) {
14864 cx.propagate();
14865 return;
14866 }
14867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14868 self.change_selections(Default::default(), window, cx, |s| {
14869 s.move_heads_with(|map, head, _| {
14870 (
14871 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14872 SelectionGoal::None,
14873 )
14874 });
14875 })
14876 }
14877
14878 pub fn select_to_end_of_previous_excerpt(
14879 &mut self,
14880 _: &SelectToEndOfPreviousExcerpt,
14881 window: &mut Window,
14882 cx: &mut Context<Self>,
14883 ) {
14884 if matches!(self.mode, EditorMode::SingleLine) {
14885 cx.propagate();
14886 return;
14887 }
14888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14889 self.change_selections(Default::default(), window, cx, |s| {
14890 s.move_heads_with(|map, head, _| {
14891 (
14892 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14893 SelectionGoal::None,
14894 )
14895 });
14896 })
14897 }
14898
14899 pub fn move_to_beginning(
14900 &mut self,
14901 _: &MoveToBeginning,
14902 window: &mut Window,
14903 cx: &mut Context<Self>,
14904 ) {
14905 if matches!(self.mode, EditorMode::SingleLine) {
14906 cx.propagate();
14907 return;
14908 }
14909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14910 self.change_selections(Default::default(), window, cx, |s| {
14911 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14912 });
14913 }
14914
14915 pub fn select_to_beginning(
14916 &mut self,
14917 _: &SelectToBeginning,
14918 window: &mut Window,
14919 cx: &mut Context<Self>,
14920 ) {
14921 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14922 selection.set_head(Point::zero(), SelectionGoal::None);
14923 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14924 self.change_selections(Default::default(), window, cx, |s| {
14925 s.select(vec![selection]);
14926 });
14927 }
14928
14929 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14930 if matches!(self.mode, EditorMode::SingleLine) {
14931 cx.propagate();
14932 return;
14933 }
14934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14935 let cursor = self.buffer.read(cx).read(cx).len();
14936 self.change_selections(Default::default(), window, cx, |s| {
14937 s.select_ranges(vec![cursor..cursor])
14938 });
14939 }
14940
14941 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14942 self.nav_history = nav_history;
14943 }
14944
14945 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14946 self.nav_history.as_ref()
14947 }
14948
14949 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14950 self.push_to_nav_history(
14951 self.selections.newest_anchor().head(),
14952 None,
14953 false,
14954 true,
14955 cx,
14956 );
14957 }
14958
14959 fn navigation_data(&self, cursor_anchor: Anchor, cx: &App) -> NavigationData {
14960 let buffer = self.buffer.read(cx).read(cx);
14961 let cursor_position = cursor_anchor.to_point(&buffer);
14962 let scroll_anchor = self.scroll_manager.anchor();
14963 let scroll_top_row = scroll_anchor.top_row(&buffer);
14964 drop(buffer);
14965
14966 NavigationData {
14967 cursor_anchor,
14968 cursor_position,
14969 scroll_anchor,
14970 scroll_top_row,
14971 }
14972 }
14973
14974 fn navigation_entry(&self, cursor_anchor: Anchor, cx: &App) -> Option<NavigationEntry> {
14975 let Some(history) = self.nav_history.clone() else {
14976 return None;
14977 };
14978 let data = self.navigation_data(cursor_anchor, cx);
14979 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
14980 }
14981
14982 fn push_to_nav_history(
14983 &mut self,
14984 cursor_anchor: Anchor,
14985 new_position: Option<Point>,
14986 is_deactivate: bool,
14987 always: bool,
14988 cx: &mut Context<Self>,
14989 ) {
14990 let data = self.navigation_data(cursor_anchor, cx);
14991 if let Some(nav_history) = self.nav_history.as_mut() {
14992 if let Some(new_position) = new_position {
14993 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
14994 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14995 return;
14996 }
14997 }
14998
14999 nav_history.push(Some(data), cx);
15000 cx.emit(EditorEvent::PushedToNavHistory {
15001 anchor: cursor_anchor,
15002 is_deactivate,
15003 })
15004 }
15005 }
15006
15007 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15008 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15009 let buffer = self.buffer.read(cx).snapshot(cx);
15010 let mut selection = self
15011 .selections
15012 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15013 selection.set_head(buffer.len(), SelectionGoal::None);
15014 self.change_selections(Default::default(), window, cx, |s| {
15015 s.select(vec![selection]);
15016 });
15017 }
15018
15019 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15021 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15022 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
15023 });
15024 }
15025
15026 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15027 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15029 let mut selections = self.selections.all::<Point>(&display_map);
15030 let max_point = display_map.buffer_snapshot().max_point();
15031 for selection in &mut selections {
15032 let rows = selection.spanned_rows(true, &display_map);
15033 selection.start = Point::new(rows.start.0, 0);
15034 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15035 selection.reversed = false;
15036 }
15037 self.change_selections(Default::default(), window, cx, |s| {
15038 s.select(selections);
15039 });
15040 }
15041
15042 pub fn split_selection_into_lines(
15043 &mut self,
15044 action: &SplitSelectionIntoLines,
15045 window: &mut Window,
15046 cx: &mut Context<Self>,
15047 ) {
15048 let selections = self
15049 .selections
15050 .all::<Point>(&self.display_snapshot(cx))
15051 .into_iter()
15052 .map(|selection| selection.start..selection.end)
15053 .collect::<Vec<_>>();
15054 self.unfold_ranges(&selections, true, true, cx);
15055
15056 let mut new_selection_ranges = Vec::new();
15057 {
15058 let buffer = self.buffer.read(cx).read(cx);
15059 for selection in selections {
15060 for row in selection.start.row..selection.end.row {
15061 let line_start = Point::new(row, 0);
15062 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15063
15064 if action.keep_selections {
15065 // Keep the selection range for each line
15066 let selection_start = if row == selection.start.row {
15067 selection.start
15068 } else {
15069 line_start
15070 };
15071 new_selection_ranges.push(selection_start..line_end);
15072 } else {
15073 // Collapse to cursor at end of line
15074 new_selection_ranges.push(line_end..line_end);
15075 }
15076 }
15077
15078 let is_multiline_selection = selection.start.row != selection.end.row;
15079 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15080 // so this action feels more ergonomic when paired with other selection operations
15081 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15082 if !should_skip_last {
15083 if action.keep_selections {
15084 if is_multiline_selection {
15085 let line_start = Point::new(selection.end.row, 0);
15086 new_selection_ranges.push(line_start..selection.end);
15087 } else {
15088 new_selection_ranges.push(selection.start..selection.end);
15089 }
15090 } else {
15091 new_selection_ranges.push(selection.end..selection.end);
15092 }
15093 }
15094 }
15095 }
15096 self.change_selections(Default::default(), window, cx, |s| {
15097 s.select_ranges(new_selection_ranges);
15098 });
15099 }
15100
15101 pub fn add_selection_above(
15102 &mut self,
15103 action: &AddSelectionAbove,
15104 window: &mut Window,
15105 cx: &mut Context<Self>,
15106 ) {
15107 self.add_selection(true, action.skip_soft_wrap, window, cx);
15108 }
15109
15110 pub fn add_selection_below(
15111 &mut self,
15112 action: &AddSelectionBelow,
15113 window: &mut Window,
15114 cx: &mut Context<Self>,
15115 ) {
15116 self.add_selection(false, action.skip_soft_wrap, window, cx);
15117 }
15118
15119 fn add_selection(
15120 &mut self,
15121 above: bool,
15122 skip_soft_wrap: bool,
15123 window: &mut Window,
15124 cx: &mut Context<Self>,
15125 ) {
15126 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15127
15128 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15129 let all_selections = self.selections.all::<Point>(&display_map);
15130 let text_layout_details = self.text_layout_details(window);
15131
15132 let (mut columnar_selections, new_selections_to_columnarize) = {
15133 if let Some(state) = self.add_selections_state.as_ref() {
15134 let columnar_selection_ids: HashSet<_> = state
15135 .groups
15136 .iter()
15137 .flat_map(|group| group.stack.iter())
15138 .copied()
15139 .collect();
15140
15141 all_selections
15142 .into_iter()
15143 .partition(|s| columnar_selection_ids.contains(&s.id))
15144 } else {
15145 (Vec::new(), all_selections)
15146 }
15147 };
15148
15149 let mut state = self
15150 .add_selections_state
15151 .take()
15152 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15153
15154 for selection in new_selections_to_columnarize {
15155 let range = selection.display_range(&display_map).sorted();
15156 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15157 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15158 let positions = start_x.min(end_x)..start_x.max(end_x);
15159 let mut stack = Vec::new();
15160 for row in range.start.row().0..=range.end.row().0 {
15161 if let Some(selection) = self.selections.build_columnar_selection(
15162 &display_map,
15163 DisplayRow(row),
15164 &positions,
15165 selection.reversed,
15166 &text_layout_details,
15167 ) {
15168 stack.push(selection.id);
15169 columnar_selections.push(selection);
15170 }
15171 }
15172 if !stack.is_empty() {
15173 if above {
15174 stack.reverse();
15175 }
15176 state.groups.push(AddSelectionsGroup { above, stack });
15177 }
15178 }
15179
15180 let mut final_selections = Vec::new();
15181 let end_row = if above {
15182 DisplayRow(0)
15183 } else {
15184 display_map.max_point().row()
15185 };
15186
15187 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15188 // positions to place new selections, so we need to keep track of the
15189 // column range of the oldest selection in each group, because
15190 // intermediate selections may have been clamped to shorter lines.
15191 // selections may have been clamped to shorter lines.
15192 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15193 let mut map = HashMap::default();
15194 for group in state.groups.iter() {
15195 if let Some(oldest_id) = group.stack.first() {
15196 if let Some(oldest_selection) =
15197 columnar_selections.iter().find(|s| s.id == *oldest_id)
15198 {
15199 let start_col = oldest_selection.start.column;
15200 let end_col = oldest_selection.end.column;
15201 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15202 for id in &group.stack {
15203 map.insert(*id, goal_columns.clone());
15204 }
15205 }
15206 }
15207 }
15208 map
15209 } else {
15210 HashMap::default()
15211 };
15212
15213 let mut last_added_item_per_group = HashMap::default();
15214 for group in state.groups.iter_mut() {
15215 if let Some(last_id) = group.stack.last() {
15216 last_added_item_per_group.insert(*last_id, group);
15217 }
15218 }
15219
15220 for selection in columnar_selections {
15221 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15222 if above == group.above {
15223 let range = selection.display_range(&display_map).sorted();
15224 debug_assert_eq!(range.start.row(), range.end.row());
15225 let row = range.start.row();
15226 let positions =
15227 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15228 Pixels::from(start)..Pixels::from(end)
15229 } else {
15230 let start_x =
15231 display_map.x_for_display_point(range.start, &text_layout_details);
15232 let end_x =
15233 display_map.x_for_display_point(range.end, &text_layout_details);
15234 start_x.min(end_x)..start_x.max(end_x)
15235 };
15236
15237 let maybe_new_selection = if skip_soft_wrap {
15238 let goal_columns = goal_columns_by_selection_id
15239 .remove(&selection.id)
15240 .unwrap_or_else(|| {
15241 let start_col = selection.start.column;
15242 let end_col = selection.end.column;
15243 start_col.min(end_col)..start_col.max(end_col)
15244 });
15245 self.selections.find_next_columnar_selection_by_buffer_row(
15246 &display_map,
15247 row,
15248 end_row,
15249 above,
15250 &goal_columns,
15251 selection.reversed,
15252 &text_layout_details,
15253 )
15254 } else {
15255 self.selections.find_next_columnar_selection_by_display_row(
15256 &display_map,
15257 row,
15258 end_row,
15259 above,
15260 &positions,
15261 selection.reversed,
15262 &text_layout_details,
15263 )
15264 };
15265
15266 if let Some(new_selection) = maybe_new_selection {
15267 group.stack.push(new_selection.id);
15268 if above {
15269 final_selections.push(new_selection);
15270 final_selections.push(selection);
15271 } else {
15272 final_selections.push(selection);
15273 final_selections.push(new_selection);
15274 }
15275 } else {
15276 final_selections.push(selection);
15277 }
15278 } else {
15279 group.stack.pop();
15280 }
15281 } else {
15282 final_selections.push(selection);
15283 }
15284 }
15285
15286 self.change_selections(Default::default(), window, cx, |s| {
15287 s.select(final_selections);
15288 });
15289
15290 let final_selection_ids: HashSet<_> = self
15291 .selections
15292 .all::<Point>(&display_map)
15293 .iter()
15294 .map(|s| s.id)
15295 .collect();
15296 state.groups.retain_mut(|group| {
15297 // selections might get merged above so we remove invalid items from stacks
15298 group.stack.retain(|id| final_selection_ids.contains(id));
15299
15300 // single selection in stack can be treated as initial state
15301 group.stack.len() > 1
15302 });
15303
15304 if !state.groups.is_empty() {
15305 self.add_selections_state = Some(state);
15306 }
15307 }
15308
15309 pub fn insert_snippet_at_selections(
15310 &mut self,
15311 action: &InsertSnippet,
15312 window: &mut Window,
15313 cx: &mut Context<Self>,
15314 ) {
15315 self.try_insert_snippet_at_selections(action, window, cx)
15316 .log_err();
15317 }
15318
15319 fn try_insert_snippet_at_selections(
15320 &mut self,
15321 action: &InsertSnippet,
15322 window: &mut Window,
15323 cx: &mut Context<Self>,
15324 ) -> Result<()> {
15325 let insertion_ranges = self
15326 .selections
15327 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15328 .into_iter()
15329 .map(|selection| selection.range())
15330 .collect_vec();
15331
15332 let snippet = if let Some(snippet_body) = &action.snippet {
15333 if action.language.is_none() && action.name.is_none() {
15334 Snippet::parse(snippet_body)?
15335 } else {
15336 bail!("`snippet` is mutually exclusive with `language` and `name`")
15337 }
15338 } else if let Some(name) = &action.name {
15339 let project = self.project().context("no project")?;
15340 let snippet_store = project.read(cx).snippets().read(cx);
15341 let snippet = snippet_store
15342 .snippets_for(action.language.clone(), cx)
15343 .into_iter()
15344 .find(|snippet| snippet.name == *name)
15345 .context("snippet not found")?;
15346 Snippet::parse(&snippet.body)?
15347 } else {
15348 // todo(andrew): open modal to select snippet
15349 bail!("`name` or `snippet` is required")
15350 };
15351
15352 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15353 }
15354
15355 fn select_match_ranges(
15356 &mut self,
15357 range: Range<MultiBufferOffset>,
15358 reversed: bool,
15359 replace_newest: bool,
15360 auto_scroll: Option<Autoscroll>,
15361 window: &mut Window,
15362 cx: &mut Context<Editor>,
15363 ) {
15364 self.unfold_ranges(
15365 std::slice::from_ref(&range),
15366 false,
15367 auto_scroll.is_some(),
15368 cx,
15369 );
15370 let effects = if let Some(scroll) = auto_scroll {
15371 SelectionEffects::scroll(scroll)
15372 } else {
15373 SelectionEffects::no_scroll()
15374 };
15375 self.change_selections(effects, window, cx, |s| {
15376 if replace_newest {
15377 s.delete(s.newest_anchor().id);
15378 }
15379 if reversed {
15380 s.insert_range(range.end..range.start);
15381 } else {
15382 s.insert_range(range);
15383 }
15384 });
15385 }
15386
15387 pub fn select_next_match_internal(
15388 &mut self,
15389 display_map: &DisplaySnapshot,
15390 replace_newest: bool,
15391 autoscroll: Option<Autoscroll>,
15392 window: &mut Window,
15393 cx: &mut Context<Self>,
15394 ) -> Result<()> {
15395 let buffer = display_map.buffer_snapshot();
15396 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15397 if let Some(mut select_next_state) = self.select_next_state.take() {
15398 let query = &select_next_state.query;
15399 if !select_next_state.done {
15400 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15401 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15402 let mut next_selected_range = None;
15403
15404 let bytes_after_last_selection =
15405 buffer.bytes_in_range(last_selection.end..buffer.len());
15406 let bytes_before_first_selection =
15407 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15408 let query_matches = query
15409 .stream_find_iter(bytes_after_last_selection)
15410 .map(|result| (last_selection.end, result))
15411 .chain(
15412 query
15413 .stream_find_iter(bytes_before_first_selection)
15414 .map(|result| (MultiBufferOffset(0), result)),
15415 );
15416
15417 for (start_offset, query_match) in query_matches {
15418 let query_match = query_match.unwrap(); // can only fail due to I/O
15419 let offset_range =
15420 start_offset + query_match.start()..start_offset + query_match.end();
15421
15422 if !select_next_state.wordwise
15423 || (!buffer.is_inside_word(offset_range.start, None)
15424 && !buffer.is_inside_word(offset_range.end, None))
15425 {
15426 let idx = selections
15427 .partition_point(|selection| selection.end <= offset_range.start);
15428 let overlaps = selections
15429 .get(idx)
15430 .map_or(false, |selection| selection.start < offset_range.end);
15431
15432 if !overlaps {
15433 next_selected_range = Some(offset_range);
15434 break;
15435 }
15436 }
15437 }
15438
15439 if let Some(next_selected_range) = next_selected_range {
15440 self.select_match_ranges(
15441 next_selected_range,
15442 last_selection.reversed,
15443 replace_newest,
15444 autoscroll,
15445 window,
15446 cx,
15447 );
15448 } else {
15449 select_next_state.done = true;
15450 }
15451 }
15452
15453 self.select_next_state = Some(select_next_state);
15454 } else {
15455 let mut only_carets = true;
15456 let mut same_text_selected = true;
15457 let mut selected_text = None;
15458
15459 let mut selections_iter = selections.iter().peekable();
15460 while let Some(selection) = selections_iter.next() {
15461 if selection.start != selection.end {
15462 only_carets = false;
15463 }
15464
15465 if same_text_selected {
15466 if selected_text.is_none() {
15467 selected_text =
15468 Some(buffer.text_for_range(selection.range()).collect::<String>());
15469 }
15470
15471 if let Some(next_selection) = selections_iter.peek() {
15472 if next_selection.len() == selection.len() {
15473 let next_selected_text = buffer
15474 .text_for_range(next_selection.range())
15475 .collect::<String>();
15476 if Some(next_selected_text) != selected_text {
15477 same_text_selected = false;
15478 selected_text = None;
15479 }
15480 } else {
15481 same_text_selected = false;
15482 selected_text = None;
15483 }
15484 }
15485 }
15486 }
15487
15488 if only_carets {
15489 for selection in &mut selections {
15490 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15491 selection.start = word_range.start;
15492 selection.end = word_range.end;
15493 selection.goal = SelectionGoal::None;
15494 selection.reversed = false;
15495 self.select_match_ranges(
15496 selection.start..selection.end,
15497 selection.reversed,
15498 replace_newest,
15499 autoscroll,
15500 window,
15501 cx,
15502 );
15503 }
15504
15505 if selections.len() == 1 {
15506 let selection = selections
15507 .last()
15508 .expect("ensured that there's only one selection");
15509 let query = buffer
15510 .text_for_range(selection.start..selection.end)
15511 .collect::<String>();
15512 let is_empty = query.is_empty();
15513 let select_state = SelectNextState {
15514 query: self.build_query(&[query], cx)?,
15515 wordwise: true,
15516 done: is_empty,
15517 };
15518 self.select_next_state = Some(select_state);
15519 } else {
15520 self.select_next_state = None;
15521 }
15522 } else if let Some(selected_text) = selected_text {
15523 self.select_next_state = Some(SelectNextState {
15524 query: self.build_query(&[selected_text], cx)?,
15525 wordwise: false,
15526 done: false,
15527 });
15528 self.select_next_match_internal(
15529 display_map,
15530 replace_newest,
15531 autoscroll,
15532 window,
15533 cx,
15534 )?;
15535 }
15536 }
15537 Ok(())
15538 }
15539
15540 pub fn select_all_matches(
15541 &mut self,
15542 _action: &SelectAllMatches,
15543 window: &mut Window,
15544 cx: &mut Context<Self>,
15545 ) -> Result<()> {
15546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15547
15548 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15549
15550 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15551 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15552 else {
15553 return Ok(());
15554 };
15555
15556 let mut new_selections = Vec::new();
15557
15558 let reversed = self
15559 .selections
15560 .oldest::<MultiBufferOffset>(&display_map)
15561 .reversed;
15562 let buffer = display_map.buffer_snapshot();
15563 let query_matches = select_next_state
15564 .query
15565 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15566
15567 for query_match in query_matches.into_iter() {
15568 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15569 let offset_range = if reversed {
15570 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15571 } else {
15572 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15573 };
15574
15575 if !select_next_state.wordwise
15576 || (!buffer.is_inside_word(offset_range.start, None)
15577 && !buffer.is_inside_word(offset_range.end, None))
15578 {
15579 new_selections.push(offset_range.start..offset_range.end);
15580 }
15581 }
15582
15583 select_next_state.done = true;
15584
15585 if new_selections.is_empty() {
15586 log::error!("bug: new_selections is empty in select_all_matches");
15587 return Ok(());
15588 }
15589
15590 self.unfold_ranges(&new_selections, false, false, cx);
15591 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15592 selections.select_ranges(new_selections)
15593 });
15594
15595 Ok(())
15596 }
15597
15598 pub fn select_next(
15599 &mut self,
15600 action: &SelectNext,
15601 window: &mut Window,
15602 cx: &mut Context<Self>,
15603 ) -> Result<()> {
15604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15606 self.select_next_match_internal(
15607 &display_map,
15608 action.replace_newest,
15609 Some(Autoscroll::newest()),
15610 window,
15611 cx,
15612 )
15613 }
15614
15615 pub fn select_previous(
15616 &mut self,
15617 action: &SelectPrevious,
15618 window: &mut Window,
15619 cx: &mut Context<Self>,
15620 ) -> Result<()> {
15621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15622 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15623 let buffer = display_map.buffer_snapshot();
15624 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15625 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15626 let query = &select_prev_state.query;
15627 if !select_prev_state.done {
15628 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15629 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15630 let mut next_selected_range = None;
15631 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15632 let bytes_before_last_selection =
15633 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15634 let bytes_after_first_selection =
15635 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15636 let query_matches = query
15637 .stream_find_iter(bytes_before_last_selection)
15638 .map(|result| (last_selection.start, result))
15639 .chain(
15640 query
15641 .stream_find_iter(bytes_after_first_selection)
15642 .map(|result| (buffer.len(), result)),
15643 );
15644 for (end_offset, query_match) in query_matches {
15645 let query_match = query_match.unwrap(); // can only fail due to I/O
15646 let offset_range =
15647 end_offset - query_match.end()..end_offset - query_match.start();
15648
15649 if !select_prev_state.wordwise
15650 || (!buffer.is_inside_word(offset_range.start, None)
15651 && !buffer.is_inside_word(offset_range.end, None))
15652 {
15653 next_selected_range = Some(offset_range);
15654 break;
15655 }
15656 }
15657
15658 if let Some(next_selected_range) = next_selected_range {
15659 self.select_match_ranges(
15660 next_selected_range,
15661 last_selection.reversed,
15662 action.replace_newest,
15663 Some(Autoscroll::newest()),
15664 window,
15665 cx,
15666 );
15667 } else {
15668 select_prev_state.done = true;
15669 }
15670 }
15671
15672 self.select_prev_state = Some(select_prev_state);
15673 } else {
15674 let mut only_carets = true;
15675 let mut same_text_selected = true;
15676 let mut selected_text = None;
15677
15678 let mut selections_iter = selections.iter().peekable();
15679 while let Some(selection) = selections_iter.next() {
15680 if selection.start != selection.end {
15681 only_carets = false;
15682 }
15683
15684 if same_text_selected {
15685 if selected_text.is_none() {
15686 selected_text =
15687 Some(buffer.text_for_range(selection.range()).collect::<String>());
15688 }
15689
15690 if let Some(next_selection) = selections_iter.peek() {
15691 if next_selection.len() == selection.len() {
15692 let next_selected_text = buffer
15693 .text_for_range(next_selection.range())
15694 .collect::<String>();
15695 if Some(next_selected_text) != selected_text {
15696 same_text_selected = false;
15697 selected_text = None;
15698 }
15699 } else {
15700 same_text_selected = false;
15701 selected_text = None;
15702 }
15703 }
15704 }
15705 }
15706
15707 if only_carets {
15708 for selection in &mut selections {
15709 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15710 selection.start = word_range.start;
15711 selection.end = word_range.end;
15712 selection.goal = SelectionGoal::None;
15713 selection.reversed = false;
15714 self.select_match_ranges(
15715 selection.start..selection.end,
15716 selection.reversed,
15717 action.replace_newest,
15718 Some(Autoscroll::newest()),
15719 window,
15720 cx,
15721 );
15722 }
15723 if selections.len() == 1 {
15724 let selection = selections
15725 .last()
15726 .expect("ensured that there's only one selection");
15727 let query = buffer
15728 .text_for_range(selection.start..selection.end)
15729 .collect::<String>();
15730 let is_empty = query.is_empty();
15731 let select_state = SelectNextState {
15732 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15733 wordwise: true,
15734 done: is_empty,
15735 };
15736 self.select_prev_state = Some(select_state);
15737 } else {
15738 self.select_prev_state = None;
15739 }
15740 } else if let Some(selected_text) = selected_text {
15741 self.select_prev_state = Some(SelectNextState {
15742 query: self
15743 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15744 wordwise: false,
15745 done: false,
15746 });
15747 self.select_previous(action, window, cx)?;
15748 }
15749 }
15750 Ok(())
15751 }
15752
15753 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15754 /// setting the case sensitivity based on the global
15755 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15756 /// editor's settings.
15757 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15758 where
15759 I: IntoIterator<Item = P>,
15760 P: AsRef<[u8]>,
15761 {
15762 let case_sensitive = self
15763 .select_next_is_case_sensitive
15764 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15765
15766 let mut builder = AhoCorasickBuilder::new();
15767 builder.ascii_case_insensitive(!case_sensitive);
15768 builder.build(patterns)
15769 }
15770
15771 pub fn find_next_match(
15772 &mut self,
15773 _: &FindNextMatch,
15774 window: &mut Window,
15775 cx: &mut Context<Self>,
15776 ) -> Result<()> {
15777 let selections = self.selections.disjoint_anchors_arc();
15778 match selections.first() {
15779 Some(first) if selections.len() >= 2 => {
15780 self.change_selections(Default::default(), window, cx, |s| {
15781 s.select_ranges([first.range()]);
15782 });
15783 }
15784 _ => self.select_next(
15785 &SelectNext {
15786 replace_newest: true,
15787 },
15788 window,
15789 cx,
15790 )?,
15791 }
15792 Ok(())
15793 }
15794
15795 pub fn find_previous_match(
15796 &mut self,
15797 _: &FindPreviousMatch,
15798 window: &mut Window,
15799 cx: &mut Context<Self>,
15800 ) -> Result<()> {
15801 let selections = self.selections.disjoint_anchors_arc();
15802 match selections.last() {
15803 Some(last) if selections.len() >= 2 => {
15804 self.change_selections(Default::default(), window, cx, |s| {
15805 s.select_ranges([last.range()]);
15806 });
15807 }
15808 _ => self.select_previous(
15809 &SelectPrevious {
15810 replace_newest: true,
15811 },
15812 window,
15813 cx,
15814 )?,
15815 }
15816 Ok(())
15817 }
15818
15819 pub fn toggle_comments(
15820 &mut self,
15821 action: &ToggleComments,
15822 window: &mut Window,
15823 cx: &mut Context<Self>,
15824 ) {
15825 if self.read_only(cx) {
15826 return;
15827 }
15828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15829 let text_layout_details = &self.text_layout_details(window);
15830 self.transact(window, cx, |this, window, cx| {
15831 let mut selections = this
15832 .selections
15833 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15834 let mut edits = Vec::new();
15835 let mut selection_edit_ranges = Vec::new();
15836 let mut last_toggled_row = None;
15837 let snapshot = this.buffer.read(cx).read(cx);
15838 let empty_str: Arc<str> = Arc::default();
15839 let mut suffixes_inserted = Vec::new();
15840 let ignore_indent = action.ignore_indent;
15841
15842 fn comment_prefix_range(
15843 snapshot: &MultiBufferSnapshot,
15844 row: MultiBufferRow,
15845 comment_prefix: &str,
15846 comment_prefix_whitespace: &str,
15847 ignore_indent: bool,
15848 ) -> Range<Point> {
15849 let indent_size = if ignore_indent {
15850 0
15851 } else {
15852 snapshot.indent_size_for_line(row).len
15853 };
15854
15855 let start = Point::new(row.0, indent_size);
15856
15857 let mut line_bytes = snapshot
15858 .bytes_in_range(start..snapshot.max_point())
15859 .flatten()
15860 .copied();
15861
15862 // If this line currently begins with the line comment prefix, then record
15863 // the range containing the prefix.
15864 if line_bytes
15865 .by_ref()
15866 .take(comment_prefix.len())
15867 .eq(comment_prefix.bytes())
15868 {
15869 // Include any whitespace that matches the comment prefix.
15870 let matching_whitespace_len = line_bytes
15871 .zip(comment_prefix_whitespace.bytes())
15872 .take_while(|(a, b)| a == b)
15873 .count() as u32;
15874 let end = Point::new(
15875 start.row,
15876 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15877 );
15878 start..end
15879 } else {
15880 start..start
15881 }
15882 }
15883
15884 fn comment_suffix_range(
15885 snapshot: &MultiBufferSnapshot,
15886 row: MultiBufferRow,
15887 comment_suffix: &str,
15888 comment_suffix_has_leading_space: bool,
15889 ) -> Range<Point> {
15890 let end = Point::new(row.0, snapshot.line_len(row));
15891 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15892
15893 let mut line_end_bytes = snapshot
15894 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15895 .flatten()
15896 .copied();
15897
15898 let leading_space_len = if suffix_start_column > 0
15899 && line_end_bytes.next() == Some(b' ')
15900 && comment_suffix_has_leading_space
15901 {
15902 1
15903 } else {
15904 0
15905 };
15906
15907 // If this line currently begins with the line comment prefix, then record
15908 // the range containing the prefix.
15909 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15910 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15911 start..end
15912 } else {
15913 end..end
15914 }
15915 }
15916
15917 // TODO: Handle selections that cross excerpts
15918 for selection in &mut selections {
15919 let start_column = snapshot
15920 .indent_size_for_line(MultiBufferRow(selection.start.row))
15921 .len;
15922 let language = if let Some(language) =
15923 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15924 {
15925 language
15926 } else {
15927 continue;
15928 };
15929
15930 selection_edit_ranges.clear();
15931
15932 // If multiple selections contain a given row, avoid processing that
15933 // row more than once.
15934 let mut start_row = MultiBufferRow(selection.start.row);
15935 if last_toggled_row == Some(start_row) {
15936 start_row = start_row.next_row();
15937 }
15938 let end_row =
15939 if selection.end.row > selection.start.row && selection.end.column == 0 {
15940 MultiBufferRow(selection.end.row - 1)
15941 } else {
15942 MultiBufferRow(selection.end.row)
15943 };
15944 last_toggled_row = Some(end_row);
15945
15946 if start_row > end_row {
15947 continue;
15948 }
15949
15950 // If the language has line comments, toggle those.
15951 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15952
15953 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15954 if ignore_indent {
15955 full_comment_prefixes = full_comment_prefixes
15956 .into_iter()
15957 .map(|s| Arc::from(s.trim_end()))
15958 .collect();
15959 }
15960
15961 if !full_comment_prefixes.is_empty() {
15962 let first_prefix = full_comment_prefixes
15963 .first()
15964 .expect("prefixes is non-empty");
15965 let prefix_trimmed_lengths = full_comment_prefixes
15966 .iter()
15967 .map(|p| p.trim_end_matches(' ').len())
15968 .collect::<SmallVec<[usize; 4]>>();
15969
15970 let mut all_selection_lines_are_comments = true;
15971
15972 for row in start_row.0..=end_row.0 {
15973 let row = MultiBufferRow(row);
15974 if start_row < end_row && snapshot.is_line_blank(row) {
15975 continue;
15976 }
15977
15978 let prefix_range = full_comment_prefixes
15979 .iter()
15980 .zip(prefix_trimmed_lengths.iter().copied())
15981 .map(|(prefix, trimmed_prefix_len)| {
15982 comment_prefix_range(
15983 snapshot.deref(),
15984 row,
15985 &prefix[..trimmed_prefix_len],
15986 &prefix[trimmed_prefix_len..],
15987 ignore_indent,
15988 )
15989 })
15990 .max_by_key(|range| range.end.column - range.start.column)
15991 .expect("prefixes is non-empty");
15992
15993 if prefix_range.is_empty() {
15994 all_selection_lines_are_comments = false;
15995 }
15996
15997 selection_edit_ranges.push(prefix_range);
15998 }
15999
16000 if all_selection_lines_are_comments {
16001 edits.extend(
16002 selection_edit_ranges
16003 .iter()
16004 .cloned()
16005 .map(|range| (range, empty_str.clone())),
16006 );
16007 } else {
16008 let min_column = selection_edit_ranges
16009 .iter()
16010 .map(|range| range.start.column)
16011 .min()
16012 .unwrap_or(0);
16013 edits.extend(selection_edit_ranges.iter().map(|range| {
16014 let position = Point::new(range.start.row, min_column);
16015 (position..position, first_prefix.clone())
16016 }));
16017 }
16018 } else if let Some(BlockCommentConfig {
16019 start: full_comment_prefix,
16020 end: comment_suffix,
16021 ..
16022 }) = language.block_comment()
16023 {
16024 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16025 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16026 let prefix_range = comment_prefix_range(
16027 snapshot.deref(),
16028 start_row,
16029 comment_prefix,
16030 comment_prefix_whitespace,
16031 ignore_indent,
16032 );
16033 let suffix_range = comment_suffix_range(
16034 snapshot.deref(),
16035 end_row,
16036 comment_suffix.trim_start_matches(' '),
16037 comment_suffix.starts_with(' '),
16038 );
16039
16040 if prefix_range.is_empty() || suffix_range.is_empty() {
16041 edits.push((
16042 prefix_range.start..prefix_range.start,
16043 full_comment_prefix.clone(),
16044 ));
16045 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16046 suffixes_inserted.push((end_row, comment_suffix.len()));
16047 } else {
16048 edits.push((prefix_range, empty_str.clone()));
16049 edits.push((suffix_range, empty_str.clone()));
16050 }
16051 } else {
16052 continue;
16053 }
16054 }
16055
16056 drop(snapshot);
16057 this.buffer.update(cx, |buffer, cx| {
16058 buffer.edit(edits, None, cx);
16059 });
16060
16061 // Adjust selections so that they end before any comment suffixes that
16062 // were inserted.
16063 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16064 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16065 let snapshot = this.buffer.read(cx).read(cx);
16066 for selection in &mut selections {
16067 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16068 match row.cmp(&MultiBufferRow(selection.end.row)) {
16069 Ordering::Less => {
16070 suffixes_inserted.next();
16071 continue;
16072 }
16073 Ordering::Greater => break,
16074 Ordering::Equal => {
16075 if selection.end.column == snapshot.line_len(row) {
16076 if selection.is_empty() {
16077 selection.start.column -= suffix_len as u32;
16078 }
16079 selection.end.column -= suffix_len as u32;
16080 }
16081 break;
16082 }
16083 }
16084 }
16085 }
16086
16087 drop(snapshot);
16088 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16089
16090 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16091 let selections_on_single_row = selections.windows(2).all(|selections| {
16092 selections[0].start.row == selections[1].start.row
16093 && selections[0].end.row == selections[1].end.row
16094 && selections[0].start.row == selections[0].end.row
16095 });
16096 let selections_selecting = selections
16097 .iter()
16098 .any(|selection| selection.start != selection.end);
16099 let advance_downwards = action.advance_downwards
16100 && selections_on_single_row
16101 && !selections_selecting
16102 && !matches!(this.mode, EditorMode::SingleLine);
16103
16104 if advance_downwards {
16105 let snapshot = this.buffer.read(cx).snapshot(cx);
16106
16107 this.change_selections(Default::default(), window, cx, |s| {
16108 s.move_cursors_with(|display_snapshot, display_point, _| {
16109 let mut point = display_point.to_point(display_snapshot);
16110 point.row += 1;
16111 point = snapshot.clip_point(point, Bias::Left);
16112 let display_point = point.to_display_point(display_snapshot);
16113 let goal = SelectionGoal::HorizontalPosition(
16114 display_snapshot
16115 .x_for_display_point(display_point, text_layout_details)
16116 .into(),
16117 );
16118 (display_point, goal)
16119 })
16120 });
16121 }
16122 });
16123 }
16124
16125 pub fn select_enclosing_symbol(
16126 &mut self,
16127 _: &SelectEnclosingSymbol,
16128 window: &mut Window,
16129 cx: &mut Context<Self>,
16130 ) {
16131 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16132
16133 let buffer = self.buffer.read(cx).snapshot(cx);
16134 let old_selections = self
16135 .selections
16136 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16137 .into_boxed_slice();
16138
16139 fn update_selection(
16140 selection: &Selection<MultiBufferOffset>,
16141 buffer_snap: &MultiBufferSnapshot,
16142 ) -> Option<Selection<MultiBufferOffset>> {
16143 let cursor = selection.head();
16144 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16145 for symbol in symbols.iter().rev() {
16146 let start = symbol.range.start.to_offset(buffer_snap);
16147 let end = symbol.range.end.to_offset(buffer_snap);
16148 let new_range = start..end;
16149 if start < selection.start || end > selection.end {
16150 return Some(Selection {
16151 id: selection.id,
16152 start: new_range.start,
16153 end: new_range.end,
16154 goal: SelectionGoal::None,
16155 reversed: selection.reversed,
16156 });
16157 }
16158 }
16159 None
16160 }
16161
16162 let mut selected_larger_symbol = false;
16163 let new_selections = old_selections
16164 .iter()
16165 .map(|selection| match update_selection(selection, &buffer) {
16166 Some(new_selection) => {
16167 if new_selection.range() != selection.range() {
16168 selected_larger_symbol = true;
16169 }
16170 new_selection
16171 }
16172 None => selection.clone(),
16173 })
16174 .collect::<Vec<_>>();
16175
16176 if selected_larger_symbol {
16177 self.change_selections(Default::default(), window, cx, |s| {
16178 s.select(new_selections);
16179 });
16180 }
16181 }
16182
16183 pub fn select_larger_syntax_node(
16184 &mut self,
16185 _: &SelectLargerSyntaxNode,
16186 window: &mut Window,
16187 cx: &mut Context<Self>,
16188 ) {
16189 let Some(visible_row_count) = self.visible_row_count() else {
16190 return;
16191 };
16192 let old_selections: Box<[_]> = self
16193 .selections
16194 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16195 .into();
16196 if old_selections.is_empty() {
16197 return;
16198 }
16199
16200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16201
16202 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16203 let buffer = self.buffer.read(cx).snapshot(cx);
16204
16205 let mut selected_larger_node = false;
16206 let mut new_selections = old_selections
16207 .iter()
16208 .map(|selection| {
16209 let old_range = selection.start..selection.end;
16210
16211 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16212 // manually select word at selection
16213 if ["string_content", "inline"].contains(&node.kind()) {
16214 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16215 // ignore if word is already selected
16216 if !word_range.is_empty() && old_range != word_range {
16217 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16218 // only select word if start and end point belongs to same word
16219 if word_range == last_word_range {
16220 selected_larger_node = true;
16221 return Selection {
16222 id: selection.id,
16223 start: word_range.start,
16224 end: word_range.end,
16225 goal: SelectionGoal::None,
16226 reversed: selection.reversed,
16227 };
16228 }
16229 }
16230 }
16231 }
16232
16233 let mut new_range = old_range.clone();
16234 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16235 new_range = range;
16236 if !node.is_named() {
16237 continue;
16238 }
16239 if !display_map.intersects_fold(new_range.start)
16240 && !display_map.intersects_fold(new_range.end)
16241 {
16242 break;
16243 }
16244 }
16245
16246 selected_larger_node |= new_range != old_range;
16247 Selection {
16248 id: selection.id,
16249 start: new_range.start,
16250 end: new_range.end,
16251 goal: SelectionGoal::None,
16252 reversed: selection.reversed,
16253 }
16254 })
16255 .collect::<Vec<_>>();
16256
16257 if !selected_larger_node {
16258 return; // don't put this call in the history
16259 }
16260
16261 // scroll based on transformation done to the last selection created by the user
16262 let (last_old, last_new) = old_selections
16263 .last()
16264 .zip(new_selections.last().cloned())
16265 .expect("old_selections isn't empty");
16266
16267 // revert selection
16268 let is_selection_reversed = {
16269 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16270 new_selections.last_mut().expect("checked above").reversed =
16271 should_newest_selection_be_reversed;
16272 should_newest_selection_be_reversed
16273 };
16274
16275 if selected_larger_node {
16276 self.select_syntax_node_history.disable_clearing = true;
16277 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16278 s.select(new_selections.clone());
16279 });
16280 self.select_syntax_node_history.disable_clearing = false;
16281 }
16282
16283 let start_row = last_new.start.to_display_point(&display_map).row().0;
16284 let end_row = last_new.end.to_display_point(&display_map).row().0;
16285 let selection_height = end_row - start_row + 1;
16286 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16287
16288 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16289 let scroll_behavior = if fits_on_the_screen {
16290 self.request_autoscroll(Autoscroll::fit(), cx);
16291 SelectSyntaxNodeScrollBehavior::FitSelection
16292 } else if is_selection_reversed {
16293 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16294 SelectSyntaxNodeScrollBehavior::CursorTop
16295 } else {
16296 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16297 SelectSyntaxNodeScrollBehavior::CursorBottom
16298 };
16299
16300 self.select_syntax_node_history.push((
16301 old_selections,
16302 scroll_behavior,
16303 is_selection_reversed,
16304 ));
16305 }
16306
16307 pub fn select_smaller_syntax_node(
16308 &mut self,
16309 _: &SelectSmallerSyntaxNode,
16310 window: &mut Window,
16311 cx: &mut Context<Self>,
16312 ) {
16313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16314
16315 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16316 self.select_syntax_node_history.pop()
16317 {
16318 if let Some(selection) = selections.last_mut() {
16319 selection.reversed = is_selection_reversed;
16320 }
16321
16322 self.select_syntax_node_history.disable_clearing = true;
16323 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16324 s.select(selections.to_vec());
16325 });
16326 self.select_syntax_node_history.disable_clearing = false;
16327
16328 match scroll_behavior {
16329 SelectSyntaxNodeScrollBehavior::CursorTop => {
16330 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16331 }
16332 SelectSyntaxNodeScrollBehavior::FitSelection => {
16333 self.request_autoscroll(Autoscroll::fit(), cx);
16334 }
16335 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16336 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16337 }
16338 }
16339 }
16340 }
16341
16342 pub fn unwrap_syntax_node(
16343 &mut self,
16344 _: &UnwrapSyntaxNode,
16345 window: &mut Window,
16346 cx: &mut Context<Self>,
16347 ) {
16348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16349
16350 let buffer = self.buffer.read(cx).snapshot(cx);
16351 let selections = self
16352 .selections
16353 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16354 .into_iter()
16355 // subtracting the offset requires sorting
16356 .sorted_by_key(|i| i.start);
16357
16358 let full_edits = selections
16359 .into_iter()
16360 .filter_map(|selection| {
16361 let child = if selection.is_empty()
16362 && let Some((_, ancestor_range)) =
16363 buffer.syntax_ancestor(selection.start..selection.end)
16364 {
16365 ancestor_range
16366 } else {
16367 selection.range()
16368 };
16369
16370 let mut parent = child.clone();
16371 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16372 parent = ancestor_range;
16373 if parent.start < child.start || parent.end > child.end {
16374 break;
16375 }
16376 }
16377
16378 if parent == child {
16379 return None;
16380 }
16381 let text = buffer.text_for_range(child).collect::<String>();
16382 Some((selection.id, parent, text))
16383 })
16384 .collect::<Vec<_>>();
16385 if full_edits.is_empty() {
16386 return;
16387 }
16388
16389 self.transact(window, cx, |this, window, cx| {
16390 this.buffer.update(cx, |buffer, cx| {
16391 buffer.edit(
16392 full_edits
16393 .iter()
16394 .map(|(_, p, t)| (p.clone(), t.clone()))
16395 .collect::<Vec<_>>(),
16396 None,
16397 cx,
16398 );
16399 });
16400 this.change_selections(Default::default(), window, cx, |s| {
16401 let mut offset = 0;
16402 let mut selections = vec![];
16403 for (id, parent, text) in full_edits {
16404 let start = parent.start - offset;
16405 offset += (parent.end - parent.start) - text.len();
16406 selections.push(Selection {
16407 id,
16408 start,
16409 end: start + text.len(),
16410 reversed: false,
16411 goal: Default::default(),
16412 });
16413 }
16414 s.select(selections);
16415 });
16416 });
16417 }
16418
16419 pub fn select_next_syntax_node(
16420 &mut self,
16421 _: &SelectNextSyntaxNode,
16422 window: &mut Window,
16423 cx: &mut Context<Self>,
16424 ) {
16425 let old_selections: Box<[_]> = self
16426 .selections
16427 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16428 .into();
16429 if old_selections.is_empty() {
16430 return;
16431 }
16432
16433 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16434
16435 let buffer = self.buffer.read(cx).snapshot(cx);
16436 let mut selected_sibling = false;
16437
16438 let new_selections = old_selections
16439 .iter()
16440 .map(|selection| {
16441 let old_range = selection.start..selection.end;
16442
16443 let old_range =
16444 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16445 let excerpt = buffer.excerpt_containing(old_range.clone());
16446
16447 if let Some(mut excerpt) = excerpt
16448 && let Some(node) = excerpt
16449 .buffer()
16450 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16451 {
16452 let new_range = excerpt.map_range_from_buffer(
16453 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16454 );
16455 selected_sibling = true;
16456 Selection {
16457 id: selection.id,
16458 start: new_range.start,
16459 end: new_range.end,
16460 goal: SelectionGoal::None,
16461 reversed: selection.reversed,
16462 }
16463 } else {
16464 selection.clone()
16465 }
16466 })
16467 .collect::<Vec<_>>();
16468
16469 if selected_sibling {
16470 self.change_selections(
16471 SelectionEffects::scroll(Autoscroll::fit()),
16472 window,
16473 cx,
16474 |s| {
16475 s.select(new_selections);
16476 },
16477 );
16478 }
16479 }
16480
16481 pub fn select_prev_syntax_node(
16482 &mut self,
16483 _: &SelectPreviousSyntaxNode,
16484 window: &mut Window,
16485 cx: &mut Context<Self>,
16486 ) {
16487 let old_selections: Box<[_]> = self
16488 .selections
16489 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16490 .into();
16491 if old_selections.is_empty() {
16492 return;
16493 }
16494
16495 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16496
16497 let buffer = self.buffer.read(cx).snapshot(cx);
16498 let mut selected_sibling = false;
16499
16500 let new_selections = old_selections
16501 .iter()
16502 .map(|selection| {
16503 let old_range = selection.start..selection.end;
16504 let old_range =
16505 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16506 let excerpt = buffer.excerpt_containing(old_range.clone());
16507
16508 if let Some(mut excerpt) = excerpt
16509 && let Some(node) = excerpt
16510 .buffer()
16511 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16512 {
16513 let new_range = excerpt.map_range_from_buffer(
16514 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16515 );
16516 selected_sibling = true;
16517 Selection {
16518 id: selection.id,
16519 start: new_range.start,
16520 end: new_range.end,
16521 goal: SelectionGoal::None,
16522 reversed: selection.reversed,
16523 }
16524 } else {
16525 selection.clone()
16526 }
16527 })
16528 .collect::<Vec<_>>();
16529
16530 if selected_sibling {
16531 self.change_selections(
16532 SelectionEffects::scroll(Autoscroll::fit()),
16533 window,
16534 cx,
16535 |s| {
16536 s.select(new_selections);
16537 },
16538 );
16539 }
16540 }
16541
16542 pub fn move_to_start_of_larger_syntax_node(
16543 &mut self,
16544 _: &MoveToStartOfLargerSyntaxNode,
16545 window: &mut Window,
16546 cx: &mut Context<Self>,
16547 ) {
16548 self.move_cursors_to_syntax_nodes(window, cx, false);
16549 }
16550
16551 pub fn move_to_end_of_larger_syntax_node(
16552 &mut self,
16553 _: &MoveToEndOfLargerSyntaxNode,
16554 window: &mut Window,
16555 cx: &mut Context<Self>,
16556 ) {
16557 self.move_cursors_to_syntax_nodes(window, cx, true);
16558 }
16559
16560 fn find_syntax_node_boundary(
16561 &self,
16562 selection_pos: MultiBufferOffset,
16563 move_to_end: bool,
16564 display_map: &DisplaySnapshot,
16565 buffer: &MultiBufferSnapshot,
16566 ) -> MultiBufferOffset {
16567 let old_range = selection_pos..selection_pos;
16568 let mut new_pos = selection_pos;
16569 let mut search_range = old_range;
16570 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16571 search_range = range.clone();
16572 if !node.is_named()
16573 || display_map.intersects_fold(range.start)
16574 || display_map.intersects_fold(range.end)
16575 // If cursor is already at the end of the syntax node, continue searching
16576 || (move_to_end && range.end == selection_pos)
16577 // If cursor is already at the start of the syntax node, continue searching
16578 || (!move_to_end && range.start == selection_pos)
16579 {
16580 continue;
16581 }
16582
16583 // If we found a string_content node, find the largest parent that is still string_content
16584 // Enables us to skip to the end of strings without taking multiple steps inside the string
16585 let (_, final_range) = if node.kind() == "string_content" {
16586 let mut current_node = node;
16587 let mut current_range = range;
16588 while let Some((parent, parent_range)) =
16589 buffer.syntax_ancestor(current_range.clone())
16590 {
16591 if parent.kind() == "string_content" {
16592 current_node = parent;
16593 current_range = parent_range;
16594 } else {
16595 break;
16596 }
16597 }
16598
16599 (current_node, current_range)
16600 } else {
16601 (node, range)
16602 };
16603
16604 new_pos = if move_to_end {
16605 final_range.end
16606 } else {
16607 final_range.start
16608 };
16609
16610 break;
16611 }
16612
16613 new_pos
16614 }
16615
16616 fn move_cursors_to_syntax_nodes(
16617 &mut self,
16618 window: &mut Window,
16619 cx: &mut Context<Self>,
16620 move_to_end: bool,
16621 ) -> bool {
16622 let old_selections: Box<[_]> = self
16623 .selections
16624 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16625 .into();
16626 if old_selections.is_empty() {
16627 return false;
16628 }
16629
16630 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16631
16632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16633 let buffer = self.buffer.read(cx).snapshot(cx);
16634
16635 let mut any_cursor_moved = false;
16636 let new_selections = old_selections
16637 .iter()
16638 .map(|selection| {
16639 if !selection.is_empty() {
16640 return selection.clone();
16641 }
16642
16643 let selection_pos = selection.head();
16644 let new_pos = self.find_syntax_node_boundary(
16645 selection_pos,
16646 move_to_end,
16647 &display_map,
16648 &buffer,
16649 );
16650
16651 any_cursor_moved |= new_pos != selection_pos;
16652
16653 Selection {
16654 id: selection.id,
16655 start: new_pos,
16656 end: new_pos,
16657 goal: SelectionGoal::None,
16658 reversed: false,
16659 }
16660 })
16661 .collect::<Vec<_>>();
16662
16663 self.change_selections(Default::default(), window, cx, |s| {
16664 s.select(new_selections);
16665 });
16666 self.request_autoscroll(Autoscroll::newest(), cx);
16667
16668 any_cursor_moved
16669 }
16670
16671 pub fn select_to_start_of_larger_syntax_node(
16672 &mut self,
16673 _: &SelectToStartOfLargerSyntaxNode,
16674 window: &mut Window,
16675 cx: &mut Context<Self>,
16676 ) {
16677 self.select_to_syntax_nodes(window, cx, false);
16678 }
16679
16680 pub fn select_to_end_of_larger_syntax_node(
16681 &mut self,
16682 _: &SelectToEndOfLargerSyntaxNode,
16683 window: &mut Window,
16684 cx: &mut Context<Self>,
16685 ) {
16686 self.select_to_syntax_nodes(window, cx, true);
16687 }
16688
16689 fn select_to_syntax_nodes(
16690 &mut self,
16691 window: &mut Window,
16692 cx: &mut Context<Self>,
16693 move_to_end: bool,
16694 ) {
16695 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16696
16697 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16698 let buffer = self.buffer.read(cx).snapshot(cx);
16699 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
16700
16701 let new_selections = old_selections
16702 .iter()
16703 .map(|selection| {
16704 let new_pos = self.find_syntax_node_boundary(
16705 selection.head(),
16706 move_to_end,
16707 &display_map,
16708 &buffer,
16709 );
16710
16711 let mut new_selection = selection.clone();
16712 new_selection.set_head(new_pos, SelectionGoal::None);
16713 new_selection
16714 })
16715 .collect::<Vec<_>>();
16716
16717 self.change_selections(Default::default(), window, cx, |s| {
16718 s.select(new_selections);
16719 });
16720 }
16721
16722 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16723 if !EditorSettings::get_global(cx).gutter.runnables {
16724 self.clear_tasks();
16725 return Task::ready(());
16726 }
16727 let project = self.project().map(Entity::downgrade);
16728 let task_sources = self.lsp_task_sources(cx);
16729 let multi_buffer = self.buffer.downgrade();
16730 cx.spawn_in(window, async move |editor, cx| {
16731 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16732 let Some(project) = project.and_then(|p| p.upgrade()) else {
16733 return;
16734 };
16735 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16736 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16737 }) else {
16738 return;
16739 };
16740
16741 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16742 if hide_runnables {
16743 return;
16744 }
16745 let new_rows =
16746 cx.background_spawn({
16747 let snapshot = display_snapshot.clone();
16748 async move {
16749 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16750 }
16751 })
16752 .await;
16753 let Ok(lsp_tasks) =
16754 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16755 else {
16756 return;
16757 };
16758 let lsp_tasks = lsp_tasks.await;
16759
16760 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16761 lsp_tasks
16762 .into_iter()
16763 .flat_map(|(kind, tasks)| {
16764 tasks.into_iter().filter_map(move |(location, task)| {
16765 Some((kind.clone(), location?, task))
16766 })
16767 })
16768 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16769 let buffer = location.target.buffer;
16770 let buffer_snapshot = buffer.read(cx).snapshot();
16771 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16772 |(excerpt_id, snapshot, _)| {
16773 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16774 display_snapshot
16775 .buffer_snapshot()
16776 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16777 } else {
16778 None
16779 }
16780 },
16781 );
16782 if let Some(offset) = offset {
16783 let task_buffer_range =
16784 location.target.range.to_point(&buffer_snapshot);
16785 let context_buffer_range =
16786 task_buffer_range.to_offset(&buffer_snapshot);
16787 let context_range = BufferOffset(context_buffer_range.start)
16788 ..BufferOffset(context_buffer_range.end);
16789
16790 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16791 .or_insert_with(|| RunnableTasks {
16792 templates: Vec::new(),
16793 offset,
16794 column: task_buffer_range.start.column,
16795 extra_variables: HashMap::default(),
16796 context_range,
16797 })
16798 .templates
16799 .push((kind, task.original_task().clone()));
16800 }
16801
16802 acc
16803 })
16804 }) else {
16805 return;
16806 };
16807
16808 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16809 buffer.language_settings(cx).tasks.prefer_lsp
16810 }) else {
16811 return;
16812 };
16813
16814 let rows = Self::runnable_rows(
16815 project,
16816 display_snapshot,
16817 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16818 new_rows,
16819 cx.clone(),
16820 )
16821 .await;
16822 editor
16823 .update(cx, |editor, _| {
16824 editor.clear_tasks();
16825 for (key, mut value) in rows {
16826 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16827 value.templates.extend(lsp_tasks.templates);
16828 }
16829
16830 editor.insert_tasks(key, value);
16831 }
16832 for (key, value) in lsp_tasks_by_rows {
16833 editor.insert_tasks(key, value);
16834 }
16835 })
16836 .ok();
16837 })
16838 }
16839 fn fetch_runnable_ranges(
16840 snapshot: &DisplaySnapshot,
16841 range: Range<Anchor>,
16842 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16843 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16844 }
16845
16846 fn runnable_rows(
16847 project: Entity<Project>,
16848 snapshot: DisplaySnapshot,
16849 prefer_lsp: bool,
16850 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16851 cx: AsyncWindowContext,
16852 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16853 cx.spawn(async move |cx| {
16854 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16855 for (run_range, mut runnable) in runnable_ranges {
16856 let Some(tasks) = cx
16857 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16858 .ok()
16859 else {
16860 continue;
16861 };
16862 let mut tasks = tasks.await;
16863
16864 if prefer_lsp {
16865 tasks.retain(|(task_kind, _)| {
16866 !matches!(task_kind, TaskSourceKind::Language { .. })
16867 });
16868 }
16869 if tasks.is_empty() {
16870 continue;
16871 }
16872
16873 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16874 let Some(row) = snapshot
16875 .buffer_snapshot()
16876 .buffer_line_for_row(MultiBufferRow(point.row))
16877 .map(|(_, range)| range.start.row)
16878 else {
16879 continue;
16880 };
16881
16882 let context_range =
16883 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16884 runnable_rows.push((
16885 (runnable.buffer_id, row),
16886 RunnableTasks {
16887 templates: tasks,
16888 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16889 context_range,
16890 column: point.column,
16891 extra_variables: runnable.extra_captures,
16892 },
16893 ));
16894 }
16895 runnable_rows
16896 })
16897 }
16898
16899 fn templates_with_tags(
16900 project: &Entity<Project>,
16901 runnable: &mut Runnable,
16902 cx: &mut App,
16903 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16904 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16905 let (worktree_id, file) = project
16906 .buffer_for_id(runnable.buffer, cx)
16907 .and_then(|buffer| buffer.read(cx).file())
16908 .map(|file| (file.worktree_id(cx), file.clone()))
16909 .unzip();
16910
16911 (
16912 project.task_store().read(cx).task_inventory().cloned(),
16913 worktree_id,
16914 file,
16915 )
16916 });
16917
16918 let tags = mem::take(&mut runnable.tags);
16919 let language = runnable.language.clone();
16920 cx.spawn(async move |cx| {
16921 let mut templates_with_tags = Vec::new();
16922 if let Some(inventory) = inventory {
16923 for RunnableTag(tag) in tags {
16924 let new_tasks = inventory.update(cx, |inventory, cx| {
16925 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16926 });
16927 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16928 move |(_, template)| {
16929 template.tags.iter().any(|source_tag| source_tag == &tag)
16930 },
16931 ));
16932 }
16933 }
16934 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16935
16936 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16937 // Strongest source wins; if we have worktree tag binding, prefer that to
16938 // global and language bindings;
16939 // if we have a global binding, prefer that to language binding.
16940 let first_mismatch = templates_with_tags
16941 .iter()
16942 .position(|(tag_source, _)| tag_source != leading_tag_source);
16943 if let Some(index) = first_mismatch {
16944 templates_with_tags.truncate(index);
16945 }
16946 }
16947
16948 templates_with_tags
16949 })
16950 }
16951
16952 pub fn move_to_enclosing_bracket(
16953 &mut self,
16954 _: &MoveToEnclosingBracket,
16955 window: &mut Window,
16956 cx: &mut Context<Self>,
16957 ) {
16958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16959 self.change_selections(Default::default(), window, cx, |s| {
16960 s.move_offsets_with(|snapshot, selection| {
16961 let Some(enclosing_bracket_ranges) =
16962 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16963 else {
16964 return;
16965 };
16966
16967 let mut best_length = usize::MAX;
16968 let mut best_inside = false;
16969 let mut best_in_bracket_range = false;
16970 let mut best_destination = None;
16971 for (open, close) in enclosing_bracket_ranges {
16972 let close = close.to_inclusive();
16973 let length = *close.end() - open.start;
16974 let inside = selection.start >= open.end && selection.end <= *close.start();
16975 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16976 || close.contains(&selection.head());
16977
16978 // If best is next to a bracket and current isn't, skip
16979 if !in_bracket_range && best_in_bracket_range {
16980 continue;
16981 }
16982
16983 // Prefer smaller lengths unless best is inside and current isn't
16984 if length > best_length && (best_inside || !inside) {
16985 continue;
16986 }
16987
16988 best_length = length;
16989 best_inside = inside;
16990 best_in_bracket_range = in_bracket_range;
16991 best_destination = Some(
16992 if close.contains(&selection.start) && close.contains(&selection.end) {
16993 if inside { open.end } else { open.start }
16994 } else if inside {
16995 *close.start()
16996 } else {
16997 *close.end()
16998 },
16999 );
17000 }
17001
17002 if let Some(destination) = best_destination {
17003 selection.collapse_to(destination, SelectionGoal::None);
17004 }
17005 })
17006 });
17007 }
17008
17009 pub fn undo_selection(
17010 &mut self,
17011 _: &UndoSelection,
17012 window: &mut Window,
17013 cx: &mut Context<Self>,
17014 ) {
17015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17016 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17017 self.selection_history.mode = SelectionHistoryMode::Undoing;
17018 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17019 this.end_selection(window, cx);
17020 this.change_selections(
17021 SelectionEffects::scroll(Autoscroll::newest()),
17022 window,
17023 cx,
17024 |s| s.select_anchors(entry.selections.to_vec()),
17025 );
17026 });
17027 self.selection_history.mode = SelectionHistoryMode::Normal;
17028
17029 self.select_next_state = entry.select_next_state;
17030 self.select_prev_state = entry.select_prev_state;
17031 self.add_selections_state = entry.add_selections_state;
17032 }
17033 }
17034
17035 pub fn redo_selection(
17036 &mut self,
17037 _: &RedoSelection,
17038 window: &mut Window,
17039 cx: &mut Context<Self>,
17040 ) {
17041 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17042 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17043 self.selection_history.mode = SelectionHistoryMode::Redoing;
17044 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17045 this.end_selection(window, cx);
17046 this.change_selections(
17047 SelectionEffects::scroll(Autoscroll::newest()),
17048 window,
17049 cx,
17050 |s| s.select_anchors(entry.selections.to_vec()),
17051 );
17052 });
17053 self.selection_history.mode = SelectionHistoryMode::Normal;
17054
17055 self.select_next_state = entry.select_next_state;
17056 self.select_prev_state = entry.select_prev_state;
17057 self.add_selections_state = entry.add_selections_state;
17058 }
17059 }
17060
17061 pub fn expand_excerpts(
17062 &mut self,
17063 action: &ExpandExcerpts,
17064 _: &mut Window,
17065 cx: &mut Context<Self>,
17066 ) {
17067 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17068 }
17069
17070 pub fn expand_excerpts_down(
17071 &mut self,
17072 action: &ExpandExcerptsDown,
17073 _: &mut Window,
17074 cx: &mut Context<Self>,
17075 ) {
17076 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17077 }
17078
17079 pub fn expand_excerpts_up(
17080 &mut self,
17081 action: &ExpandExcerptsUp,
17082 _: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) {
17085 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17086 }
17087
17088 pub fn expand_excerpts_for_direction(
17089 &mut self,
17090 lines: u32,
17091 direction: ExpandExcerptDirection,
17092 cx: &mut Context<Self>,
17093 ) {
17094 let selections = self.selections.disjoint_anchors_arc();
17095
17096 let lines = if lines == 0 {
17097 EditorSettings::get_global(cx).expand_excerpt_lines
17098 } else {
17099 lines
17100 };
17101
17102 let snapshot = self.buffer.read(cx).snapshot(cx);
17103 let mut excerpt_ids = selections
17104 .iter()
17105 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17106 .collect::<Vec<_>>();
17107 excerpt_ids.sort();
17108 excerpt_ids.dedup();
17109
17110 if self.delegate_expand_excerpts {
17111 cx.emit(EditorEvent::ExpandExcerptsRequested {
17112 excerpt_ids,
17113 lines,
17114 direction,
17115 });
17116 return;
17117 }
17118
17119 self.buffer.update(cx, |buffer, cx| {
17120 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17121 })
17122 }
17123
17124 pub fn expand_excerpt(
17125 &mut self,
17126 excerpt: ExcerptId,
17127 direction: ExpandExcerptDirection,
17128 window: &mut Window,
17129 cx: &mut Context<Self>,
17130 ) {
17131 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17132
17133 if self.delegate_expand_excerpts {
17134 cx.emit(EditorEvent::ExpandExcerptsRequested {
17135 excerpt_ids: vec![excerpt],
17136 lines: lines_to_expand,
17137 direction,
17138 });
17139 return;
17140 }
17141
17142 let current_scroll_position = self.scroll_position(cx);
17143 let mut scroll = None;
17144
17145 if direction == ExpandExcerptDirection::Down {
17146 let multi_buffer = self.buffer.read(cx);
17147 let snapshot = multi_buffer.snapshot(cx);
17148 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17149 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17150 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17151 {
17152 let buffer_snapshot = buffer.read(cx).snapshot();
17153 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17154 let last_row = buffer_snapshot.max_point().row;
17155 let lines_below = last_row.saturating_sub(excerpt_end_row);
17156 if lines_below >= lines_to_expand {
17157 scroll = Some(
17158 current_scroll_position
17159 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17160 );
17161 }
17162 }
17163 }
17164 if direction == ExpandExcerptDirection::Up
17165 && self
17166 .buffer
17167 .read(cx)
17168 .snapshot(cx)
17169 .excerpt_before(excerpt)
17170 .is_none()
17171 {
17172 scroll = Some(current_scroll_position);
17173 }
17174
17175 self.buffer.update(cx, |buffer, cx| {
17176 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17177 });
17178
17179 if let Some(new_scroll_position) = scroll {
17180 self.set_scroll_position(new_scroll_position, window, cx);
17181 }
17182 }
17183
17184 pub fn go_to_singleton_buffer_point(
17185 &mut self,
17186 point: Point,
17187 window: &mut Window,
17188 cx: &mut Context<Self>,
17189 ) {
17190 self.go_to_singleton_buffer_range(point..point, window, cx);
17191 }
17192
17193 pub fn go_to_singleton_buffer_range(
17194 &mut self,
17195 range: Range<Point>,
17196 window: &mut Window,
17197 cx: &mut Context<Self>,
17198 ) {
17199 let multibuffer = self.buffer().read(cx);
17200 let Some(buffer) = multibuffer.as_singleton() else {
17201 return;
17202 };
17203 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17204 return;
17205 };
17206 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17207 return;
17208 };
17209 self.change_selections(
17210 SelectionEffects::default().nav_history(true),
17211 window,
17212 cx,
17213 |s| s.select_anchor_ranges([start..end]),
17214 );
17215 }
17216
17217 pub fn go_to_diagnostic(
17218 &mut self,
17219 action: &GoToDiagnostic,
17220 window: &mut Window,
17221 cx: &mut Context<Self>,
17222 ) {
17223 if !self.diagnostics_enabled() {
17224 return;
17225 }
17226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17227 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17228 }
17229
17230 pub fn go_to_prev_diagnostic(
17231 &mut self,
17232 action: &GoToPreviousDiagnostic,
17233 window: &mut Window,
17234 cx: &mut Context<Self>,
17235 ) {
17236 if !self.diagnostics_enabled() {
17237 return;
17238 }
17239 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17240 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17241 }
17242
17243 pub fn go_to_diagnostic_impl(
17244 &mut self,
17245 direction: Direction,
17246 severity: GoToDiagnosticSeverityFilter,
17247 window: &mut Window,
17248 cx: &mut Context<Self>,
17249 ) {
17250 let buffer = self.buffer.read(cx).snapshot(cx);
17251 let selection = self
17252 .selections
17253 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17254
17255 let mut active_group_id = None;
17256 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17257 && active_group.active_range.start.to_offset(&buffer) == selection.start
17258 {
17259 active_group_id = Some(active_group.group_id);
17260 }
17261
17262 fn filtered<'a>(
17263 severity: GoToDiagnosticSeverityFilter,
17264 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17265 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17266 diagnostics
17267 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17268 .filter(|entry| entry.range.start != entry.range.end)
17269 .filter(|entry| !entry.diagnostic.is_unnecessary)
17270 }
17271
17272 let before = filtered(
17273 severity,
17274 buffer
17275 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17276 .filter(|entry| entry.range.start <= selection.start),
17277 );
17278 let after = filtered(
17279 severity,
17280 buffer
17281 .diagnostics_in_range(selection.start..buffer.len())
17282 .filter(|entry| entry.range.start >= selection.start),
17283 );
17284
17285 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17286 if direction == Direction::Prev {
17287 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17288 {
17289 for diagnostic in prev_diagnostics.into_iter().rev() {
17290 if diagnostic.range.start != selection.start
17291 || active_group_id
17292 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17293 {
17294 found = Some(diagnostic);
17295 break 'outer;
17296 }
17297 }
17298 }
17299 } else {
17300 for diagnostic in after.chain(before) {
17301 if diagnostic.range.start != selection.start
17302 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17303 {
17304 found = Some(diagnostic);
17305 break;
17306 }
17307 }
17308 }
17309 let Some(next_diagnostic) = found else {
17310 return;
17311 };
17312
17313 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17314 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17315 return;
17316 };
17317 let snapshot = self.snapshot(window, cx);
17318 if snapshot.intersects_fold(next_diagnostic.range.start) {
17319 self.unfold_ranges(
17320 std::slice::from_ref(&next_diagnostic.range),
17321 true,
17322 false,
17323 cx,
17324 );
17325 }
17326 self.change_selections(Default::default(), window, cx, |s| {
17327 s.select_ranges(vec![
17328 next_diagnostic.range.start..next_diagnostic.range.start,
17329 ])
17330 });
17331 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17332 self.refresh_edit_prediction(false, true, window, cx);
17333 }
17334
17335 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17337 let snapshot = self.snapshot(window, cx);
17338 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17339 self.go_to_hunk_before_or_after_position(
17340 &snapshot,
17341 selection.head(),
17342 Direction::Next,
17343 window,
17344 cx,
17345 );
17346 }
17347
17348 pub fn go_to_hunk_before_or_after_position(
17349 &mut self,
17350 snapshot: &EditorSnapshot,
17351 position: Point,
17352 direction: Direction,
17353 window: &mut Window,
17354 cx: &mut Context<Editor>,
17355 ) {
17356 let row = if direction == Direction::Next {
17357 self.hunk_after_position(snapshot, position)
17358 .map(|hunk| hunk.row_range.start)
17359 } else {
17360 self.hunk_before_position(snapshot, position)
17361 };
17362
17363 if let Some(row) = row {
17364 let destination = Point::new(row.0, 0);
17365 let autoscroll = Autoscroll::center();
17366
17367 self.unfold_ranges(&[destination..destination], false, false, cx);
17368 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17369 s.select_ranges([destination..destination]);
17370 });
17371 }
17372 }
17373
17374 fn hunk_after_position(
17375 &mut self,
17376 snapshot: &EditorSnapshot,
17377 position: Point,
17378 ) -> Option<MultiBufferDiffHunk> {
17379 snapshot
17380 .buffer_snapshot()
17381 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17382 .find(|hunk| hunk.row_range.start.0 > position.row)
17383 .or_else(|| {
17384 snapshot
17385 .buffer_snapshot()
17386 .diff_hunks_in_range(Point::zero()..position)
17387 .find(|hunk| hunk.row_range.end.0 < position.row)
17388 })
17389 }
17390
17391 fn go_to_prev_hunk(
17392 &mut self,
17393 _: &GoToPreviousHunk,
17394 window: &mut Window,
17395 cx: &mut Context<Self>,
17396 ) {
17397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17398 let snapshot = self.snapshot(window, cx);
17399 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17400 self.go_to_hunk_before_or_after_position(
17401 &snapshot,
17402 selection.head(),
17403 Direction::Prev,
17404 window,
17405 cx,
17406 );
17407 }
17408
17409 fn hunk_before_position(
17410 &mut self,
17411 snapshot: &EditorSnapshot,
17412 position: Point,
17413 ) -> Option<MultiBufferRow> {
17414 snapshot
17415 .buffer_snapshot()
17416 .diff_hunk_before(position)
17417 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17418 }
17419
17420 fn go_to_next_change(
17421 &mut self,
17422 _: &GoToNextChange,
17423 window: &mut Window,
17424 cx: &mut Context<Self>,
17425 ) {
17426 if let Some(selections) = self
17427 .change_list
17428 .next_change(1, Direction::Next)
17429 .map(|s| s.to_vec())
17430 {
17431 self.change_selections(Default::default(), window, cx, |s| {
17432 let map = s.display_snapshot();
17433 s.select_display_ranges(selections.iter().map(|a| {
17434 let point = a.to_display_point(&map);
17435 point..point
17436 }))
17437 })
17438 }
17439 }
17440
17441 fn go_to_previous_change(
17442 &mut self,
17443 _: &GoToPreviousChange,
17444 window: &mut Window,
17445 cx: &mut Context<Self>,
17446 ) {
17447 if let Some(selections) = self
17448 .change_list
17449 .next_change(1, Direction::Prev)
17450 .map(|s| s.to_vec())
17451 {
17452 self.change_selections(Default::default(), window, cx, |s| {
17453 let map = s.display_snapshot();
17454 s.select_display_ranges(selections.iter().map(|a| {
17455 let point = a.to_display_point(&map);
17456 point..point
17457 }))
17458 })
17459 }
17460 }
17461
17462 pub fn go_to_next_document_highlight(
17463 &mut self,
17464 _: &GoToNextDocumentHighlight,
17465 window: &mut Window,
17466 cx: &mut Context<Self>,
17467 ) {
17468 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17469 }
17470
17471 pub fn go_to_prev_document_highlight(
17472 &mut self,
17473 _: &GoToPreviousDocumentHighlight,
17474 window: &mut Window,
17475 cx: &mut Context<Self>,
17476 ) {
17477 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17478 }
17479
17480 pub fn go_to_document_highlight_before_or_after_position(
17481 &mut self,
17482 direction: Direction,
17483 window: &mut Window,
17484 cx: &mut Context<Editor>,
17485 ) {
17486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17487 let snapshot = self.snapshot(window, cx);
17488 let buffer = &snapshot.buffer_snapshot();
17489 let position = self
17490 .selections
17491 .newest::<Point>(&snapshot.display_snapshot)
17492 .head();
17493 let anchor_position = buffer.anchor_after(position);
17494
17495 // Get all document highlights (both read and write)
17496 let mut all_highlights = Vec::new();
17497
17498 if let Some((_, read_highlights)) = self
17499 .background_highlights
17500 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17501 {
17502 all_highlights.extend(read_highlights.iter());
17503 }
17504
17505 if let Some((_, write_highlights)) = self
17506 .background_highlights
17507 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17508 {
17509 all_highlights.extend(write_highlights.iter());
17510 }
17511
17512 if all_highlights.is_empty() {
17513 return;
17514 }
17515
17516 // Sort highlights by position
17517 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17518
17519 let target_highlight = match direction {
17520 Direction::Next => {
17521 // Find the first highlight after the current position
17522 all_highlights
17523 .iter()
17524 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17525 }
17526 Direction::Prev => {
17527 // Find the last highlight before the current position
17528 all_highlights
17529 .iter()
17530 .rev()
17531 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17532 }
17533 };
17534
17535 if let Some(highlight) = target_highlight {
17536 let destination = highlight.start.to_point(buffer);
17537 let autoscroll = Autoscroll::center();
17538
17539 self.unfold_ranges(&[destination..destination], false, false, cx);
17540 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17541 s.select_ranges([destination..destination]);
17542 });
17543 }
17544 }
17545
17546 fn go_to_line<T: 'static>(
17547 &mut self,
17548 position: Anchor,
17549 highlight_color: Option<Hsla>,
17550 window: &mut Window,
17551 cx: &mut Context<Self>,
17552 ) {
17553 let snapshot = self.snapshot(window, cx).display_snapshot;
17554 let position = position.to_point(&snapshot.buffer_snapshot());
17555 let start = snapshot
17556 .buffer_snapshot()
17557 .clip_point(Point::new(position.row, 0), Bias::Left);
17558 let end = start + Point::new(1, 0);
17559 let start = snapshot.buffer_snapshot().anchor_before(start);
17560 let end = snapshot.buffer_snapshot().anchor_before(end);
17561
17562 self.highlight_rows::<T>(
17563 start..end,
17564 highlight_color
17565 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17566 Default::default(),
17567 cx,
17568 );
17569
17570 if self.buffer.read(cx).is_singleton() {
17571 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17572 }
17573 }
17574
17575 pub fn go_to_definition(
17576 &mut self,
17577 _: &GoToDefinition,
17578 window: &mut Window,
17579 cx: &mut Context<Self>,
17580 ) -> Task<Result<Navigated>> {
17581 let definition =
17582 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17583 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17584 cx.spawn_in(window, async move |editor, cx| {
17585 if definition.await? == Navigated::Yes {
17586 return Ok(Navigated::Yes);
17587 }
17588 match fallback_strategy {
17589 GoToDefinitionFallback::None => Ok(Navigated::No),
17590 GoToDefinitionFallback::FindAllReferences => {
17591 match editor.update_in(cx, |editor, window, cx| {
17592 editor.find_all_references(&FindAllReferences::default(), window, cx)
17593 })? {
17594 Some(references) => references.await,
17595 None => Ok(Navigated::No),
17596 }
17597 }
17598 }
17599 })
17600 }
17601
17602 pub fn go_to_declaration(
17603 &mut self,
17604 _: &GoToDeclaration,
17605 window: &mut Window,
17606 cx: &mut Context<Self>,
17607 ) -> Task<Result<Navigated>> {
17608 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17609 }
17610
17611 pub fn go_to_declaration_split(
17612 &mut self,
17613 _: &GoToDeclaration,
17614 window: &mut Window,
17615 cx: &mut Context<Self>,
17616 ) -> Task<Result<Navigated>> {
17617 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17618 }
17619
17620 pub fn go_to_implementation(
17621 &mut self,
17622 _: &GoToImplementation,
17623 window: &mut Window,
17624 cx: &mut Context<Self>,
17625 ) -> Task<Result<Navigated>> {
17626 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17627 }
17628
17629 pub fn go_to_implementation_split(
17630 &mut self,
17631 _: &GoToImplementationSplit,
17632 window: &mut Window,
17633 cx: &mut Context<Self>,
17634 ) -> Task<Result<Navigated>> {
17635 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17636 }
17637
17638 pub fn go_to_type_definition(
17639 &mut self,
17640 _: &GoToTypeDefinition,
17641 window: &mut Window,
17642 cx: &mut Context<Self>,
17643 ) -> Task<Result<Navigated>> {
17644 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17645 }
17646
17647 pub fn go_to_definition_split(
17648 &mut self,
17649 _: &GoToDefinitionSplit,
17650 window: &mut Window,
17651 cx: &mut Context<Self>,
17652 ) -> Task<Result<Navigated>> {
17653 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17654 }
17655
17656 pub fn go_to_type_definition_split(
17657 &mut self,
17658 _: &GoToTypeDefinitionSplit,
17659 window: &mut Window,
17660 cx: &mut Context<Self>,
17661 ) -> Task<Result<Navigated>> {
17662 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17663 }
17664
17665 fn go_to_definition_of_kind(
17666 &mut self,
17667 kind: GotoDefinitionKind,
17668 split: bool,
17669 window: &mut Window,
17670 cx: &mut Context<Self>,
17671 ) -> Task<Result<Navigated>> {
17672 let Some(provider) = self.semantics_provider.clone() else {
17673 return Task::ready(Ok(Navigated::No));
17674 };
17675 let head = self
17676 .selections
17677 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17678 .head();
17679 let buffer = self.buffer.read(cx);
17680 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17681 return Task::ready(Ok(Navigated::No));
17682 };
17683 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17684 return Task::ready(Ok(Navigated::No));
17685 };
17686
17687 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17688
17689 cx.spawn_in(window, async move |editor, cx| {
17690 let Some(definitions) = definitions.await? else {
17691 return Ok(Navigated::No);
17692 };
17693 let navigated = editor
17694 .update_in(cx, |editor, window, cx| {
17695 editor.navigate_to_hover_links(
17696 Some(kind),
17697 definitions
17698 .into_iter()
17699 .filter(|location| {
17700 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17701 })
17702 .map(HoverLink::Text)
17703 .collect::<Vec<_>>(),
17704 nav_entry,
17705 split,
17706 window,
17707 cx,
17708 )
17709 })?
17710 .await?;
17711 anyhow::Ok(navigated)
17712 })
17713 }
17714
17715 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17716 let selection = self.selections.newest_anchor();
17717 let head = selection.head();
17718 let tail = selection.tail();
17719
17720 let Some((buffer, start_position)) =
17721 self.buffer.read(cx).text_anchor_for_position(head, cx)
17722 else {
17723 return;
17724 };
17725
17726 let end_position = if head != tail {
17727 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17728 return;
17729 };
17730 Some(pos)
17731 } else {
17732 None
17733 };
17734
17735 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17736 let url = if let Some(end_pos) = end_position {
17737 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17738 } else {
17739 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17740 };
17741
17742 if let Some(url) = url {
17743 cx.update(|window, cx| {
17744 if parse_zed_link(&url, cx).is_some() {
17745 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17746 } else {
17747 cx.open_url(&url);
17748 }
17749 })?;
17750 }
17751
17752 anyhow::Ok(())
17753 });
17754
17755 url_finder.detach();
17756 }
17757
17758 pub fn open_selected_filename(
17759 &mut self,
17760 _: &OpenSelectedFilename,
17761 window: &mut Window,
17762 cx: &mut Context<Self>,
17763 ) {
17764 let Some(workspace) = self.workspace() else {
17765 return;
17766 };
17767
17768 let position = self.selections.newest_anchor().head();
17769
17770 let Some((buffer, buffer_position)) =
17771 self.buffer.read(cx).text_anchor_for_position(position, cx)
17772 else {
17773 return;
17774 };
17775
17776 let project = self.project.clone();
17777
17778 cx.spawn_in(window, async move |_, cx| {
17779 let result = find_file(&buffer, project, buffer_position, cx).await;
17780
17781 if let Some((_, path)) = result {
17782 workspace
17783 .update_in(cx, |workspace, window, cx| {
17784 workspace.open_resolved_path(path, window, cx)
17785 })?
17786 .await?;
17787 }
17788 anyhow::Ok(())
17789 })
17790 .detach();
17791 }
17792
17793 pub(crate) fn navigate_to_hover_links(
17794 &mut self,
17795 kind: Option<GotoDefinitionKind>,
17796 definitions: Vec<HoverLink>,
17797 origin: Option<NavigationEntry>,
17798 split: bool,
17799 window: &mut Window,
17800 cx: &mut Context<Editor>,
17801 ) -> Task<Result<Navigated>> {
17802 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17803 let mut first_url_or_file = None;
17804 let definitions: Vec<_> = definitions
17805 .into_iter()
17806 .filter_map(|def| match def {
17807 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17808 HoverLink::InlayHint(lsp_location, server_id) => {
17809 let computation =
17810 self.compute_target_location(lsp_location, server_id, window, cx);
17811 Some(cx.background_spawn(computation))
17812 }
17813 HoverLink::Url(url) => {
17814 first_url_or_file = Some(Either::Left(url));
17815 None
17816 }
17817 HoverLink::File(path) => {
17818 first_url_or_file = Some(Either::Right(path));
17819 None
17820 }
17821 })
17822 .collect();
17823
17824 let workspace = self.workspace();
17825
17826 cx.spawn_in(window, async move |editor, cx| {
17827 let locations: Vec<Location> = future::join_all(definitions)
17828 .await
17829 .into_iter()
17830 .filter_map(|location| location.transpose())
17831 .collect::<Result<_>>()
17832 .context("location tasks")?;
17833 let mut locations = cx.update(|_, cx| {
17834 locations
17835 .into_iter()
17836 .map(|location| {
17837 let buffer = location.buffer.read(cx);
17838 (location.buffer, location.range.to_point(buffer))
17839 })
17840 .into_group_map()
17841 })?;
17842 let mut num_locations = 0;
17843 for ranges in locations.values_mut() {
17844 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17845 ranges.dedup();
17846 num_locations += ranges.len();
17847 }
17848
17849 if num_locations > 1 {
17850 let tab_kind = match kind {
17851 Some(GotoDefinitionKind::Implementation) => "Implementations",
17852 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17853 Some(GotoDefinitionKind::Declaration) => "Declarations",
17854 Some(GotoDefinitionKind::Type) => "Types",
17855 };
17856 let title = editor
17857 .update_in(cx, |_, _, cx| {
17858 let target = locations
17859 .iter()
17860 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17861 .map(|(buffer, location)| {
17862 buffer
17863 .read(cx)
17864 .text_for_range(location.clone())
17865 .collect::<String>()
17866 })
17867 .filter(|text| !text.contains('\n'))
17868 .unique()
17869 .take(3)
17870 .join(", ");
17871 if target.is_empty() {
17872 tab_kind.to_owned()
17873 } else {
17874 format!("{tab_kind} for {target}")
17875 }
17876 })
17877 .context("buffer title")?;
17878
17879 let Some(workspace) = workspace else {
17880 return Ok(Navigated::No);
17881 };
17882
17883 let opened = workspace
17884 .update_in(cx, |workspace, window, cx| {
17885 let allow_preview = PreviewTabsSettings::get_global(cx)
17886 .enable_preview_multibuffer_from_code_navigation;
17887 if let Some((target_editor, target_pane)) =
17888 Self::open_locations_in_multibuffer(
17889 workspace,
17890 locations,
17891 title,
17892 split,
17893 allow_preview,
17894 MultibufferSelectionMode::First,
17895 window,
17896 cx,
17897 )
17898 {
17899 // We create our own nav history instead of using
17900 // `target_editor.nav_history` because `nav_history`
17901 // seems to be populated asynchronously when an item
17902 // is added to a pane
17903 let mut nav_history = target_pane
17904 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17905 target_editor.update(cx, |editor, cx| {
17906 let nav_data = editor
17907 .navigation_data(editor.selections.newest_anchor().head(), cx);
17908 let target =
17909 Some(nav_history.navigation_entry(Some(
17910 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17911 )));
17912 nav_history.push_tag(origin, target);
17913 })
17914 }
17915 })
17916 .is_ok();
17917
17918 anyhow::Ok(Navigated::from_bool(opened))
17919 } else if num_locations == 0 {
17920 // If there is one url or file, open it directly
17921 match first_url_or_file {
17922 Some(Either::Left(url)) => {
17923 cx.update(|window, cx| {
17924 if parse_zed_link(&url, cx).is_some() {
17925 window
17926 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17927 } else {
17928 cx.open_url(&url);
17929 }
17930 })?;
17931 Ok(Navigated::Yes)
17932 }
17933 Some(Either::Right(path)) => {
17934 // TODO(andrew): respect preview tab settings
17935 // `enable_keep_preview_on_code_navigation` and
17936 // `enable_preview_file_from_code_navigation`
17937 let Some(workspace) = workspace else {
17938 return Ok(Navigated::No);
17939 };
17940 workspace
17941 .update_in(cx, |workspace, window, cx| {
17942 workspace.open_resolved_path(path, window, cx)
17943 })?
17944 .await?;
17945 Ok(Navigated::Yes)
17946 }
17947 None => Ok(Navigated::No),
17948 }
17949 } else {
17950 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17951 let target_range = target_ranges.first().unwrap().clone();
17952
17953 editor.update_in(cx, |editor, window, cx| {
17954 let range = editor.range_for_match(&target_range);
17955 let range = collapse_multiline_range(range);
17956
17957 if !split
17958 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17959 {
17960 editor.go_to_singleton_buffer_range(range, window, cx);
17961
17962 let target =
17963 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
17964 if let Some(mut nav_history) = editor.nav_history.clone() {
17965 nav_history.push_tag(origin, target);
17966 }
17967 } else {
17968 let Some(workspace) = workspace else {
17969 return Navigated::No;
17970 };
17971 let pane = workspace.read(cx).active_pane().clone();
17972 window.defer(cx, move |window, cx| {
17973 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
17974 workspace.update(cx, |workspace, cx| {
17975 let pane = if split {
17976 workspace.adjacent_pane(window, cx)
17977 } else {
17978 workspace.active_pane().clone()
17979 };
17980
17981 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17982 let keep_old_preview = preview_tabs_settings
17983 .enable_keep_preview_on_code_navigation;
17984 let allow_new_preview = preview_tabs_settings
17985 .enable_preview_file_from_code_navigation;
17986
17987 let editor = workspace.open_project_item(
17988 pane.clone(),
17989 target_buffer.clone(),
17990 true,
17991 true,
17992 keep_old_preview,
17993 allow_new_preview,
17994 window,
17995 cx,
17996 );
17997 (editor, pane)
17998 });
17999 // We create our own nav history instead of using
18000 // `target_editor.nav_history` because `nav_history`
18001 // seems to be populated asynchronously when an item
18002 // is added to a pane
18003 let mut nav_history = target_pane
18004 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18005 target_editor.update(cx, |target_editor, cx| {
18006 // When selecting a definition in a different buffer, disable the nav history
18007 // to avoid creating a history entry at the previous cursor location.
18008 pane.update(cx, |pane, _| pane.disable_history());
18009 target_editor.go_to_singleton_buffer_range(range, window, cx);
18010
18011 let nav_data = target_editor.navigation_data(
18012 target_editor.selections.newest_anchor().head(),
18013 cx,
18014 );
18015 let target =
18016 Some(nav_history.navigation_entry(Some(
18017 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18018 )));
18019 nav_history.push_tag(origin, target);
18020 pane.update(cx, |pane, _| pane.enable_history());
18021 });
18022 });
18023 }
18024 Navigated::Yes
18025 })
18026 }
18027 })
18028 }
18029
18030 fn compute_target_location(
18031 &self,
18032 lsp_location: lsp::Location,
18033 server_id: LanguageServerId,
18034 window: &mut Window,
18035 cx: &mut Context<Self>,
18036 ) -> Task<anyhow::Result<Option<Location>>> {
18037 let Some(project) = self.project.clone() else {
18038 return Task::ready(Ok(None));
18039 };
18040
18041 cx.spawn_in(window, async move |editor, cx| {
18042 let location_task = editor.update(cx, |_, cx| {
18043 project.update(cx, |project, cx| {
18044 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18045 })
18046 })?;
18047 let location = Some({
18048 let target_buffer_handle = location_task.await.context("open local buffer")?;
18049 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18050 let target_start = target_buffer
18051 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18052 let target_end = target_buffer
18053 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18054 target_buffer.anchor_after(target_start)
18055 ..target_buffer.anchor_before(target_end)
18056 });
18057 Location {
18058 buffer: target_buffer_handle,
18059 range,
18060 }
18061 });
18062 Ok(location)
18063 })
18064 }
18065
18066 fn go_to_next_reference(
18067 &mut self,
18068 _: &GoToNextReference,
18069 window: &mut Window,
18070 cx: &mut Context<Self>,
18071 ) {
18072 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18073 if let Some(task) = task {
18074 task.detach();
18075 };
18076 }
18077
18078 fn go_to_prev_reference(
18079 &mut self,
18080 _: &GoToPreviousReference,
18081 window: &mut Window,
18082 cx: &mut Context<Self>,
18083 ) {
18084 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18085 if let Some(task) = task {
18086 task.detach();
18087 };
18088 }
18089
18090 pub fn go_to_reference_before_or_after_position(
18091 &mut self,
18092 direction: Direction,
18093 count: usize,
18094 window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) -> Option<Task<Result<()>>> {
18097 let selection = self.selections.newest_anchor();
18098 let head = selection.head();
18099
18100 let multi_buffer = self.buffer.read(cx);
18101
18102 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18103 let workspace = self.workspace()?;
18104 let project = workspace.read(cx).project().clone();
18105 let references =
18106 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18107 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18108 let Some(locations) = references.await? else {
18109 return Ok(());
18110 };
18111
18112 if locations.is_empty() {
18113 // totally normal - the cursor may be on something which is not
18114 // a symbol (e.g. a keyword)
18115 log::info!("no references found under cursor");
18116 return Ok(());
18117 }
18118
18119 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18120
18121 let (locations, current_location_index) =
18122 multi_buffer.update(cx, |multi_buffer, cx| {
18123 let mut locations = locations
18124 .into_iter()
18125 .filter_map(|loc| {
18126 let start = multi_buffer.buffer_anchor_to_anchor(
18127 &loc.buffer,
18128 loc.range.start,
18129 cx,
18130 )?;
18131 let end = multi_buffer.buffer_anchor_to_anchor(
18132 &loc.buffer,
18133 loc.range.end,
18134 cx,
18135 )?;
18136 Some(start..end)
18137 })
18138 .collect::<Vec<_>>();
18139
18140 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18141 // There is an O(n) implementation, but given this list will be
18142 // small (usually <100 items), the extra O(log(n)) factor isn't
18143 // worth the (surprisingly large amount of) extra complexity.
18144 locations
18145 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18146
18147 let head_offset = head.to_offset(&multi_buffer_snapshot);
18148
18149 let current_location_index = locations.iter().position(|loc| {
18150 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18151 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18152 });
18153
18154 (locations, current_location_index)
18155 });
18156
18157 let Some(current_location_index) = current_location_index else {
18158 // This indicates something has gone wrong, because we already
18159 // handle the "no references" case above
18160 log::error!(
18161 "failed to find current reference under cursor. Total references: {}",
18162 locations.len()
18163 );
18164 return Ok(());
18165 };
18166
18167 let destination_location_index = match direction {
18168 Direction::Next => (current_location_index + count) % locations.len(),
18169 Direction::Prev => {
18170 (current_location_index + locations.len() - count % locations.len())
18171 % locations.len()
18172 }
18173 };
18174
18175 // TODO(cameron): is this needed?
18176 // the thinking is to avoid "jumping to the current location" (avoid
18177 // polluting "jumplist" in vim terms)
18178 if current_location_index == destination_location_index {
18179 return Ok(());
18180 }
18181
18182 let Range { start, end } = locations[destination_location_index];
18183
18184 editor.update_in(cx, |editor, window, cx| {
18185 let effects = SelectionEffects::default();
18186
18187 editor.unfold_ranges(&[start..end], false, false, cx);
18188 editor.change_selections(effects, window, cx, |s| {
18189 s.select_ranges([start..start]);
18190 });
18191 })?;
18192
18193 Ok(())
18194 }))
18195 }
18196
18197 pub fn find_all_references(
18198 &mut self,
18199 action: &FindAllReferences,
18200 window: &mut Window,
18201 cx: &mut Context<Self>,
18202 ) -> Option<Task<Result<Navigated>>> {
18203 let always_open_multibuffer = action.always_open_multibuffer;
18204 let selection = self.selections.newest_anchor();
18205 let multi_buffer = self.buffer.read(cx);
18206 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18207 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18208 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18209 let head = selection_offset.head();
18210
18211 let head_anchor = multi_buffer_snapshot.anchor_at(
18212 head,
18213 if head < selection_offset.tail() {
18214 Bias::Right
18215 } else {
18216 Bias::Left
18217 },
18218 );
18219
18220 match self
18221 .find_all_references_task_sources
18222 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18223 {
18224 Ok(_) => {
18225 log::info!(
18226 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18227 );
18228 return None;
18229 }
18230 Err(i) => {
18231 self.find_all_references_task_sources.insert(i, head_anchor);
18232 }
18233 }
18234
18235 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18236 let workspace = self.workspace()?;
18237 let project = workspace.read(cx).project().clone();
18238 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18239 Some(cx.spawn_in(window, async move |editor, cx| {
18240 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18241 if let Ok(i) = editor
18242 .find_all_references_task_sources
18243 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18244 {
18245 editor.find_all_references_task_sources.remove(i);
18246 }
18247 });
18248
18249 let Some(locations) = references.await? else {
18250 return anyhow::Ok(Navigated::No);
18251 };
18252 let mut locations = cx.update(|_, cx| {
18253 locations
18254 .into_iter()
18255 .map(|location| {
18256 let buffer = location.buffer.read(cx);
18257 (location.buffer, location.range.to_point(buffer))
18258 })
18259 // if special-casing the single-match case, remove ranges
18260 // that intersect current selection
18261 .filter(|(location_buffer, location)| {
18262 if always_open_multibuffer || &buffer != location_buffer {
18263 return true;
18264 }
18265
18266 !location.contains_inclusive(&selection_point.range())
18267 })
18268 .into_group_map()
18269 })?;
18270 if locations.is_empty() {
18271 return anyhow::Ok(Navigated::No);
18272 }
18273 for ranges in locations.values_mut() {
18274 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18275 ranges.dedup();
18276 }
18277 let mut num_locations = 0;
18278 for ranges in locations.values_mut() {
18279 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18280 ranges.dedup();
18281 num_locations += ranges.len();
18282 }
18283
18284 if num_locations == 1 && !always_open_multibuffer {
18285 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18286 let target_range = target_ranges.first().unwrap().clone();
18287
18288 return editor.update_in(cx, |editor, window, cx| {
18289 let range = target_range.to_point(target_buffer.read(cx));
18290 let range = editor.range_for_match(&range);
18291 let range = range.start..range.start;
18292
18293 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18294 editor.go_to_singleton_buffer_range(range, window, cx);
18295 } else {
18296 let pane = workspace.read(cx).active_pane().clone();
18297 window.defer(cx, move |window, cx| {
18298 let target_editor: Entity<Self> =
18299 workspace.update(cx, |workspace, cx| {
18300 let pane = workspace.active_pane().clone();
18301
18302 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18303 let keep_old_preview = preview_tabs_settings
18304 .enable_keep_preview_on_code_navigation;
18305 let allow_new_preview = preview_tabs_settings
18306 .enable_preview_file_from_code_navigation;
18307
18308 workspace.open_project_item(
18309 pane,
18310 target_buffer.clone(),
18311 true,
18312 true,
18313 keep_old_preview,
18314 allow_new_preview,
18315 window,
18316 cx,
18317 )
18318 });
18319 target_editor.update(cx, |target_editor, cx| {
18320 // When selecting a definition in a different buffer, disable the nav history
18321 // to avoid creating a history entry at the previous cursor location.
18322 pane.update(cx, |pane, _| pane.disable_history());
18323 target_editor.go_to_singleton_buffer_range(range, window, cx);
18324 pane.update(cx, |pane, _| pane.enable_history());
18325 });
18326 });
18327 }
18328 Navigated::No
18329 });
18330 }
18331
18332 workspace.update_in(cx, |workspace, window, cx| {
18333 let target = locations
18334 .iter()
18335 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18336 .map(|(buffer, location)| {
18337 buffer
18338 .read(cx)
18339 .text_for_range(location.clone())
18340 .collect::<String>()
18341 })
18342 .filter(|text| !text.contains('\n'))
18343 .unique()
18344 .take(3)
18345 .join(", ");
18346 let title = if target.is_empty() {
18347 "References".to_owned()
18348 } else {
18349 format!("References to {target}")
18350 };
18351 let allow_preview = PreviewTabsSettings::get_global(cx)
18352 .enable_preview_multibuffer_from_code_navigation;
18353 Self::open_locations_in_multibuffer(
18354 workspace,
18355 locations,
18356 title,
18357 false,
18358 allow_preview,
18359 MultibufferSelectionMode::First,
18360 window,
18361 cx,
18362 );
18363 Navigated::Yes
18364 })
18365 }))
18366 }
18367
18368 /// Opens a multibuffer with the given project locations in it.
18369 pub fn open_locations_in_multibuffer(
18370 workspace: &mut Workspace,
18371 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18372 title: String,
18373 split: bool,
18374 allow_preview: bool,
18375 multibuffer_selection_mode: MultibufferSelectionMode,
18376 window: &mut Window,
18377 cx: &mut Context<Workspace>,
18378 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18379 if locations.is_empty() {
18380 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18381 return None;
18382 }
18383
18384 let capability = workspace.project().read(cx).capability();
18385 let mut ranges = <Vec<Range<Anchor>>>::new();
18386
18387 // a key to find existing multibuffer editors with the same set of locations
18388 // to prevent us from opening more and more multibuffer tabs for searches and the like
18389 let mut key = (title.clone(), vec![]);
18390 let excerpt_buffer = cx.new(|cx| {
18391 let key = &mut key.1;
18392 let mut multibuffer = MultiBuffer::new(capability);
18393 for (buffer, mut ranges_for_buffer) in locations {
18394 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18395 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18396 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18397 PathKey::for_buffer(&buffer, cx),
18398 buffer.clone(),
18399 ranges_for_buffer,
18400 multibuffer_context_lines(cx),
18401 cx,
18402 );
18403 ranges.extend(new_ranges)
18404 }
18405
18406 multibuffer.with_title(title)
18407 });
18408 let existing = workspace.active_pane().update(cx, |pane, cx| {
18409 pane.items()
18410 .filter_map(|item| item.downcast::<Editor>())
18411 .find(|editor| {
18412 editor
18413 .read(cx)
18414 .lookup_key
18415 .as_ref()
18416 .and_then(|it| {
18417 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18418 })
18419 .is_some_and(|it| *it == key)
18420 })
18421 });
18422 let was_existing = existing.is_some();
18423 let editor = existing.unwrap_or_else(|| {
18424 cx.new(|cx| {
18425 let mut editor = Editor::for_multibuffer(
18426 excerpt_buffer,
18427 Some(workspace.project().clone()),
18428 window,
18429 cx,
18430 );
18431 editor.lookup_key = Some(Box::new(key));
18432 editor
18433 })
18434 });
18435 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18436 MultibufferSelectionMode::First => {
18437 if let Some(first_range) = ranges.first() {
18438 editor.change_selections(
18439 SelectionEffects::no_scroll(),
18440 window,
18441 cx,
18442 |selections| {
18443 selections.clear_disjoint();
18444 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18445 },
18446 );
18447 }
18448 editor.highlight_background::<Self>(
18449 &ranges,
18450 |_, theme| theme.colors().editor_highlighted_line_background,
18451 cx,
18452 );
18453 }
18454 MultibufferSelectionMode::All => {
18455 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18456 selections.clear_disjoint();
18457 selections.select_anchor_ranges(ranges);
18458 });
18459 }
18460 });
18461
18462 let item = Box::new(editor.clone());
18463
18464 let pane = if split {
18465 workspace.adjacent_pane(window, cx)
18466 } else {
18467 workspace.active_pane().clone()
18468 };
18469 let activate_pane = split;
18470
18471 let mut destination_index = None;
18472 pane.update(cx, |pane, cx| {
18473 if allow_preview && !was_existing {
18474 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18475 }
18476 if was_existing && !allow_preview {
18477 pane.unpreview_item_if_preview(item.item_id());
18478 }
18479 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18480 });
18481
18482 Some((editor, pane))
18483 }
18484
18485 pub fn rename(
18486 &mut self,
18487 _: &Rename,
18488 window: &mut Window,
18489 cx: &mut Context<Self>,
18490 ) -> Option<Task<Result<()>>> {
18491 use language::ToOffset as _;
18492
18493 let provider = self.semantics_provider.clone()?;
18494 let selection = self.selections.newest_anchor().clone();
18495 let (cursor_buffer, cursor_buffer_position) = self
18496 .buffer
18497 .read(cx)
18498 .text_anchor_for_position(selection.head(), cx)?;
18499 let (tail_buffer, cursor_buffer_position_end) = self
18500 .buffer
18501 .read(cx)
18502 .text_anchor_for_position(selection.tail(), cx)?;
18503 if tail_buffer != cursor_buffer {
18504 return None;
18505 }
18506
18507 let snapshot = cursor_buffer.read(cx).snapshot();
18508 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18509 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18510 let prepare_rename = provider
18511 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18512 .unwrap_or_else(|| Task::ready(Ok(None)));
18513 drop(snapshot);
18514
18515 Some(cx.spawn_in(window, async move |this, cx| {
18516 let rename_range = if let Some(range) = prepare_rename.await? {
18517 Some(range)
18518 } else {
18519 this.update(cx, |this, cx| {
18520 let buffer = this.buffer.read(cx).snapshot(cx);
18521 let mut buffer_highlights = this
18522 .document_highlights_for_position(selection.head(), &buffer)
18523 .filter(|highlight| {
18524 highlight.start.excerpt_id == selection.head().excerpt_id
18525 && highlight.end.excerpt_id == selection.head().excerpt_id
18526 });
18527 buffer_highlights
18528 .next()
18529 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18530 })?
18531 };
18532 if let Some(rename_range) = rename_range {
18533 this.update_in(cx, |this, window, cx| {
18534 let snapshot = cursor_buffer.read(cx).snapshot();
18535 let rename_buffer_range = rename_range.to_offset(&snapshot);
18536 let cursor_offset_in_rename_range =
18537 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18538 let cursor_offset_in_rename_range_end =
18539 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18540
18541 this.take_rename(false, window, cx);
18542 let buffer = this.buffer.read(cx).read(cx);
18543 let cursor_offset = selection.head().to_offset(&buffer);
18544 let rename_start =
18545 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18546 let rename_end = rename_start + rename_buffer_range.len();
18547 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18548 let mut old_highlight_id = None;
18549 let old_name: Arc<str> = buffer
18550 .chunks(rename_start..rename_end, true)
18551 .map(|chunk| {
18552 if old_highlight_id.is_none() {
18553 old_highlight_id = chunk.syntax_highlight_id;
18554 }
18555 chunk.text
18556 })
18557 .collect::<String>()
18558 .into();
18559
18560 drop(buffer);
18561
18562 // Position the selection in the rename editor so that it matches the current selection.
18563 this.show_local_selections = false;
18564 let rename_editor = cx.new(|cx| {
18565 let mut editor = Editor::single_line(window, cx);
18566 editor.buffer.update(cx, |buffer, cx| {
18567 buffer.edit(
18568 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18569 None,
18570 cx,
18571 )
18572 });
18573 let cursor_offset_in_rename_range =
18574 MultiBufferOffset(cursor_offset_in_rename_range);
18575 let cursor_offset_in_rename_range_end =
18576 MultiBufferOffset(cursor_offset_in_rename_range_end);
18577 let rename_selection_range = match cursor_offset_in_rename_range
18578 .cmp(&cursor_offset_in_rename_range_end)
18579 {
18580 Ordering::Equal => {
18581 editor.select_all(&SelectAll, window, cx);
18582 return editor;
18583 }
18584 Ordering::Less => {
18585 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18586 }
18587 Ordering::Greater => {
18588 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18589 }
18590 };
18591 if rename_selection_range.end.0 > old_name.len() {
18592 editor.select_all(&SelectAll, window, cx);
18593 } else {
18594 editor.change_selections(Default::default(), window, cx, |s| {
18595 s.select_ranges([rename_selection_range]);
18596 });
18597 }
18598 editor
18599 });
18600 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18601 if e == &EditorEvent::Focused {
18602 cx.emit(EditorEvent::FocusedIn)
18603 }
18604 })
18605 .detach();
18606
18607 let write_highlights =
18608 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18609 let read_highlights =
18610 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18611 let ranges = write_highlights
18612 .iter()
18613 .flat_map(|(_, ranges)| ranges.iter())
18614 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18615 .cloned()
18616 .collect();
18617
18618 this.highlight_text::<Rename>(
18619 ranges,
18620 HighlightStyle {
18621 fade_out: Some(0.6),
18622 ..Default::default()
18623 },
18624 cx,
18625 );
18626 let rename_focus_handle = rename_editor.focus_handle(cx);
18627 window.focus(&rename_focus_handle, cx);
18628 let block_id = this.insert_blocks(
18629 [BlockProperties {
18630 style: BlockStyle::Flex,
18631 placement: BlockPlacement::Below(range.start),
18632 height: Some(1),
18633 render: Arc::new({
18634 let rename_editor = rename_editor.clone();
18635 move |cx: &mut BlockContext| {
18636 let mut text_style = cx.editor_style.text.clone();
18637 if let Some(highlight_style) = old_highlight_id
18638 .and_then(|h| h.style(&cx.editor_style.syntax))
18639 {
18640 text_style = text_style.highlight(highlight_style);
18641 }
18642 div()
18643 .block_mouse_except_scroll()
18644 .pl(cx.anchor_x)
18645 .child(EditorElement::new(
18646 &rename_editor,
18647 EditorStyle {
18648 background: cx.theme().system().transparent,
18649 local_player: cx.editor_style.local_player,
18650 text: text_style,
18651 scrollbar_width: cx.editor_style.scrollbar_width,
18652 syntax: cx.editor_style.syntax.clone(),
18653 status: cx.editor_style.status.clone(),
18654 inlay_hints_style: HighlightStyle {
18655 font_weight: Some(FontWeight::BOLD),
18656 ..make_inlay_hints_style(cx.app)
18657 },
18658 edit_prediction_styles: make_suggestion_styles(
18659 cx.app,
18660 ),
18661 ..EditorStyle::default()
18662 },
18663 ))
18664 .into_any_element()
18665 }
18666 }),
18667 priority: 0,
18668 }],
18669 Some(Autoscroll::fit()),
18670 cx,
18671 )[0];
18672 this.pending_rename = Some(RenameState {
18673 range,
18674 old_name,
18675 editor: rename_editor,
18676 block_id,
18677 });
18678 })?;
18679 }
18680
18681 Ok(())
18682 }))
18683 }
18684
18685 pub fn confirm_rename(
18686 &mut self,
18687 _: &ConfirmRename,
18688 window: &mut Window,
18689 cx: &mut Context<Self>,
18690 ) -> Option<Task<Result<()>>> {
18691 let rename = self.take_rename(false, window, cx)?;
18692 let workspace = self.workspace()?.downgrade();
18693 let (buffer, start) = self
18694 .buffer
18695 .read(cx)
18696 .text_anchor_for_position(rename.range.start, cx)?;
18697 let (end_buffer, _) = self
18698 .buffer
18699 .read(cx)
18700 .text_anchor_for_position(rename.range.end, cx)?;
18701 if buffer != end_buffer {
18702 return None;
18703 }
18704
18705 let old_name = rename.old_name;
18706 let new_name = rename.editor.read(cx).text(cx);
18707
18708 let rename = self.semantics_provider.as_ref()?.perform_rename(
18709 &buffer,
18710 start,
18711 new_name.clone(),
18712 cx,
18713 )?;
18714
18715 Some(cx.spawn_in(window, async move |editor, cx| {
18716 let project_transaction = rename.await?;
18717 Self::open_project_transaction(
18718 &editor,
18719 workspace,
18720 project_transaction,
18721 format!("Rename: {} → {}", old_name, new_name),
18722 cx,
18723 )
18724 .await?;
18725
18726 editor.update(cx, |editor, cx| {
18727 editor.refresh_document_highlights(cx);
18728 })?;
18729 Ok(())
18730 }))
18731 }
18732
18733 fn take_rename(
18734 &mut self,
18735 moving_cursor: bool,
18736 window: &mut Window,
18737 cx: &mut Context<Self>,
18738 ) -> Option<RenameState> {
18739 let rename = self.pending_rename.take()?;
18740 if rename.editor.focus_handle(cx).is_focused(window) {
18741 window.focus(&self.focus_handle, cx);
18742 }
18743
18744 self.remove_blocks(
18745 [rename.block_id].into_iter().collect(),
18746 Some(Autoscroll::fit()),
18747 cx,
18748 );
18749 self.clear_highlights::<Rename>(cx);
18750 self.show_local_selections = true;
18751
18752 if moving_cursor {
18753 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18754 editor
18755 .selections
18756 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18757 .head()
18758 });
18759
18760 // Update the selection to match the position of the selection inside
18761 // the rename editor.
18762 let snapshot = self.buffer.read(cx).read(cx);
18763 let rename_range = rename.range.to_offset(&snapshot);
18764 let cursor_in_editor = snapshot
18765 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18766 .min(rename_range.end);
18767 drop(snapshot);
18768
18769 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18770 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18771 });
18772 } else {
18773 self.refresh_document_highlights(cx);
18774 }
18775
18776 Some(rename)
18777 }
18778
18779 pub fn pending_rename(&self) -> Option<&RenameState> {
18780 self.pending_rename.as_ref()
18781 }
18782
18783 fn format(
18784 &mut self,
18785 _: &Format,
18786 window: &mut Window,
18787 cx: &mut Context<Self>,
18788 ) -> Option<Task<Result<()>>> {
18789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18790
18791 let project = match &self.project {
18792 Some(project) => project.clone(),
18793 None => return None,
18794 };
18795
18796 Some(self.perform_format(
18797 project,
18798 FormatTrigger::Manual,
18799 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18800 window,
18801 cx,
18802 ))
18803 }
18804
18805 fn format_selections(
18806 &mut self,
18807 _: &FormatSelections,
18808 window: &mut Window,
18809 cx: &mut Context<Self>,
18810 ) -> Option<Task<Result<()>>> {
18811 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18812
18813 let project = match &self.project {
18814 Some(project) => project.clone(),
18815 None => return None,
18816 };
18817
18818 let ranges = self
18819 .selections
18820 .all_adjusted(&self.display_snapshot(cx))
18821 .into_iter()
18822 .map(|selection| selection.range())
18823 .collect_vec();
18824
18825 Some(self.perform_format(
18826 project,
18827 FormatTrigger::Manual,
18828 FormatTarget::Ranges(ranges),
18829 window,
18830 cx,
18831 ))
18832 }
18833
18834 fn perform_format(
18835 &mut self,
18836 project: Entity<Project>,
18837 trigger: FormatTrigger,
18838 target: FormatTarget,
18839 window: &mut Window,
18840 cx: &mut Context<Self>,
18841 ) -> Task<Result<()>> {
18842 let buffer = self.buffer.clone();
18843 let (buffers, target) = match target {
18844 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18845 FormatTarget::Ranges(selection_ranges) => {
18846 let multi_buffer = buffer.read(cx);
18847 let snapshot = multi_buffer.read(cx);
18848 let mut buffers = HashSet::default();
18849 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18850 BTreeMap::new();
18851 for selection_range in selection_ranges {
18852 for (buffer, buffer_range, _) in
18853 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
18854 {
18855 let buffer_id = buffer.remote_id();
18856 let start = buffer.anchor_before(buffer_range.start);
18857 let end = buffer.anchor_after(buffer_range.end);
18858 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18859 buffer_id_to_ranges
18860 .entry(buffer_id)
18861 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18862 .or_insert_with(|| vec![start..end]);
18863 }
18864 }
18865 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18866 }
18867 };
18868
18869 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18870 let selections_prev = transaction_id_prev
18871 .and_then(|transaction_id_prev| {
18872 // default to selections as they were after the last edit, if we have them,
18873 // instead of how they are now.
18874 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18875 // will take you back to where you made the last edit, instead of staying where you scrolled
18876 self.selection_history
18877 .transaction(transaction_id_prev)
18878 .map(|t| t.0.clone())
18879 })
18880 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18881
18882 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18883 let format = project.update(cx, |project, cx| {
18884 project.format(buffers, target, true, trigger, cx)
18885 });
18886
18887 cx.spawn_in(window, async move |editor, cx| {
18888 let transaction = futures::select_biased! {
18889 transaction = format.log_err().fuse() => transaction,
18890 () = timeout => {
18891 log::warn!("timed out waiting for formatting");
18892 None
18893 }
18894 };
18895
18896 buffer.update(cx, |buffer, cx| {
18897 if let Some(transaction) = transaction
18898 && !buffer.is_singleton()
18899 {
18900 buffer.push_transaction(&transaction.0, cx);
18901 }
18902 cx.notify();
18903 });
18904
18905 if let Some(transaction_id_now) =
18906 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18907 {
18908 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18909 if has_new_transaction {
18910 editor
18911 .update(cx, |editor, _| {
18912 editor
18913 .selection_history
18914 .insert_transaction(transaction_id_now, selections_prev);
18915 })
18916 .ok();
18917 }
18918 }
18919
18920 Ok(())
18921 })
18922 }
18923
18924 fn organize_imports(
18925 &mut self,
18926 _: &OrganizeImports,
18927 window: &mut Window,
18928 cx: &mut Context<Self>,
18929 ) -> Option<Task<Result<()>>> {
18930 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18931 let project = match &self.project {
18932 Some(project) => project.clone(),
18933 None => return None,
18934 };
18935 Some(self.perform_code_action_kind(
18936 project,
18937 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18938 window,
18939 cx,
18940 ))
18941 }
18942
18943 fn perform_code_action_kind(
18944 &mut self,
18945 project: Entity<Project>,
18946 kind: CodeActionKind,
18947 window: &mut Window,
18948 cx: &mut Context<Self>,
18949 ) -> Task<Result<()>> {
18950 let buffer = self.buffer.clone();
18951 let buffers = buffer.read(cx).all_buffers();
18952 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18953 let apply_action = project.update(cx, |project, cx| {
18954 project.apply_code_action_kind(buffers, kind, true, cx)
18955 });
18956 cx.spawn_in(window, async move |_, cx| {
18957 let transaction = futures::select_biased! {
18958 () = timeout => {
18959 log::warn!("timed out waiting for executing code action");
18960 None
18961 }
18962 transaction = apply_action.log_err().fuse() => transaction,
18963 };
18964 buffer.update(cx, |buffer, cx| {
18965 // check if we need this
18966 if let Some(transaction) = transaction
18967 && !buffer.is_singleton()
18968 {
18969 buffer.push_transaction(&transaction.0, cx);
18970 }
18971 cx.notify();
18972 });
18973 Ok(())
18974 })
18975 }
18976
18977 pub fn restart_language_server(
18978 &mut self,
18979 _: &RestartLanguageServer,
18980 _: &mut Window,
18981 cx: &mut Context<Self>,
18982 ) {
18983 if let Some(project) = self.project.clone() {
18984 self.buffer.update(cx, |multi_buffer, cx| {
18985 project.update(cx, |project, cx| {
18986 project.restart_language_servers_for_buffers(
18987 multi_buffer.all_buffers().into_iter().collect(),
18988 HashSet::default(),
18989 cx,
18990 );
18991 });
18992 })
18993 }
18994 }
18995
18996 pub fn stop_language_server(
18997 &mut self,
18998 _: &StopLanguageServer,
18999 _: &mut Window,
19000 cx: &mut Context<Self>,
19001 ) {
19002 if let Some(project) = self.project.clone() {
19003 self.buffer.update(cx, |multi_buffer, cx| {
19004 project.update(cx, |project, cx| {
19005 project.stop_language_servers_for_buffers(
19006 multi_buffer.all_buffers().into_iter().collect(),
19007 HashSet::default(),
19008 cx,
19009 );
19010 });
19011 });
19012 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19013 }
19014 }
19015
19016 fn cancel_language_server_work(
19017 workspace: &mut Workspace,
19018 _: &actions::CancelLanguageServerWork,
19019 _: &mut Window,
19020 cx: &mut Context<Workspace>,
19021 ) {
19022 let project = workspace.project();
19023 let buffers = workspace
19024 .active_item(cx)
19025 .and_then(|item| item.act_as::<Editor>(cx))
19026 .map_or(HashSet::default(), |editor| {
19027 editor.read(cx).buffer.read(cx).all_buffers()
19028 });
19029 project.update(cx, |project, cx| {
19030 project.cancel_language_server_work_for_buffers(buffers, cx);
19031 });
19032 }
19033
19034 fn show_character_palette(
19035 &mut self,
19036 _: &ShowCharacterPalette,
19037 window: &mut Window,
19038 _: &mut Context<Self>,
19039 ) {
19040 window.show_character_palette();
19041 }
19042
19043 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19044 if !self.diagnostics_enabled() {
19045 return;
19046 }
19047
19048 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19049 let buffer = self.buffer.read(cx).snapshot(cx);
19050 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19051 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19052 let is_valid = buffer
19053 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19054 .any(|entry| {
19055 entry.diagnostic.is_primary
19056 && !entry.range.is_empty()
19057 && entry.range.start == primary_range_start
19058 && entry.diagnostic.message == active_diagnostics.active_message
19059 });
19060
19061 if !is_valid {
19062 self.dismiss_diagnostics(cx);
19063 }
19064 }
19065 }
19066
19067 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19068 match &self.active_diagnostics {
19069 ActiveDiagnostic::Group(group) => Some(group),
19070 _ => None,
19071 }
19072 }
19073
19074 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19075 if !self.diagnostics_enabled() {
19076 return;
19077 }
19078 self.dismiss_diagnostics(cx);
19079 self.active_diagnostics = ActiveDiagnostic::All;
19080 }
19081
19082 fn activate_diagnostics(
19083 &mut self,
19084 buffer_id: BufferId,
19085 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19086 window: &mut Window,
19087 cx: &mut Context<Self>,
19088 ) {
19089 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19090 return;
19091 }
19092 self.dismiss_diagnostics(cx);
19093 let snapshot = self.snapshot(window, cx);
19094 let buffer = self.buffer.read(cx).snapshot(cx);
19095 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19096 return;
19097 };
19098
19099 let diagnostic_group = buffer
19100 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19101 .collect::<Vec<_>>();
19102
19103 let language_registry = self
19104 .project()
19105 .map(|project| project.read(cx).languages().clone());
19106
19107 let blocks = renderer.render_group(
19108 diagnostic_group,
19109 buffer_id,
19110 snapshot,
19111 cx.weak_entity(),
19112 language_registry,
19113 cx,
19114 );
19115
19116 let blocks = self.display_map.update(cx, |display_map, cx| {
19117 display_map.insert_blocks(blocks, cx).into_iter().collect()
19118 });
19119 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19120 active_range: buffer.anchor_before(diagnostic.range.start)
19121 ..buffer.anchor_after(diagnostic.range.end),
19122 active_message: diagnostic.diagnostic.message.clone(),
19123 group_id: diagnostic.diagnostic.group_id,
19124 blocks,
19125 });
19126 cx.notify();
19127 }
19128
19129 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19130 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19131 return;
19132 };
19133
19134 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19135 if let ActiveDiagnostic::Group(group) = prev {
19136 self.display_map.update(cx, |display_map, cx| {
19137 display_map.remove_blocks(group.blocks, cx);
19138 });
19139 cx.notify();
19140 }
19141 }
19142
19143 /// Disable inline diagnostics rendering for this editor.
19144 pub fn disable_inline_diagnostics(&mut self) {
19145 self.inline_diagnostics_enabled = false;
19146 self.inline_diagnostics_update = Task::ready(());
19147 self.inline_diagnostics.clear();
19148 }
19149
19150 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19151 self.diagnostics_enabled = false;
19152 self.dismiss_diagnostics(cx);
19153 self.inline_diagnostics_update = Task::ready(());
19154 self.inline_diagnostics.clear();
19155 }
19156
19157 pub fn disable_word_completions(&mut self) {
19158 self.word_completions_enabled = false;
19159 }
19160
19161 pub fn diagnostics_enabled(&self) -> bool {
19162 self.diagnostics_enabled && self.mode.is_full()
19163 }
19164
19165 pub fn inline_diagnostics_enabled(&self) -> bool {
19166 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19167 }
19168
19169 pub fn show_inline_diagnostics(&self) -> bool {
19170 self.show_inline_diagnostics
19171 }
19172
19173 pub fn toggle_inline_diagnostics(
19174 &mut self,
19175 _: &ToggleInlineDiagnostics,
19176 window: &mut Window,
19177 cx: &mut Context<Editor>,
19178 ) {
19179 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19180 self.refresh_inline_diagnostics(false, window, cx);
19181 }
19182
19183 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19184 self.diagnostics_max_severity = severity;
19185 self.display_map.update(cx, |display_map, _| {
19186 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19187 });
19188 }
19189
19190 pub fn toggle_diagnostics(
19191 &mut self,
19192 _: &ToggleDiagnostics,
19193 window: &mut Window,
19194 cx: &mut Context<Editor>,
19195 ) {
19196 if !self.diagnostics_enabled() {
19197 return;
19198 }
19199
19200 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19201 EditorSettings::get_global(cx)
19202 .diagnostics_max_severity
19203 .filter(|severity| severity != &DiagnosticSeverity::Off)
19204 .unwrap_or(DiagnosticSeverity::Hint)
19205 } else {
19206 DiagnosticSeverity::Off
19207 };
19208 self.set_max_diagnostics_severity(new_severity, cx);
19209 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19210 self.active_diagnostics = ActiveDiagnostic::None;
19211 self.inline_diagnostics_update = Task::ready(());
19212 self.inline_diagnostics.clear();
19213 } else {
19214 self.refresh_inline_diagnostics(false, window, cx);
19215 }
19216
19217 cx.notify();
19218 }
19219
19220 pub fn toggle_minimap(
19221 &mut self,
19222 _: &ToggleMinimap,
19223 window: &mut Window,
19224 cx: &mut Context<Editor>,
19225 ) {
19226 if self.supports_minimap(cx) {
19227 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19228 }
19229 }
19230
19231 fn refresh_inline_diagnostics(
19232 &mut self,
19233 debounce: bool,
19234 window: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) {
19237 let max_severity = ProjectSettings::get_global(cx)
19238 .diagnostics
19239 .inline
19240 .max_severity
19241 .unwrap_or(self.diagnostics_max_severity);
19242
19243 if !self.inline_diagnostics_enabled()
19244 || !self.diagnostics_enabled()
19245 || !self.show_inline_diagnostics
19246 || max_severity == DiagnosticSeverity::Off
19247 {
19248 self.inline_diagnostics_update = Task::ready(());
19249 self.inline_diagnostics.clear();
19250 return;
19251 }
19252
19253 let debounce_ms = ProjectSettings::get_global(cx)
19254 .diagnostics
19255 .inline
19256 .update_debounce_ms;
19257 let debounce = if debounce && debounce_ms > 0 {
19258 Some(Duration::from_millis(debounce_ms))
19259 } else {
19260 None
19261 };
19262 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19263 if let Some(debounce) = debounce {
19264 cx.background_executor().timer(debounce).await;
19265 }
19266 let Some(snapshot) = editor.upgrade().map(|editor| {
19267 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19268 }) else {
19269 return;
19270 };
19271
19272 let new_inline_diagnostics = cx
19273 .background_spawn(async move {
19274 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19275 for diagnostic_entry in
19276 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19277 {
19278 let message = diagnostic_entry
19279 .diagnostic
19280 .message
19281 .split_once('\n')
19282 .map(|(line, _)| line)
19283 .map(SharedString::new)
19284 .unwrap_or_else(|| {
19285 SharedString::new(&*diagnostic_entry.diagnostic.message)
19286 });
19287 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19288 let (Ok(i) | Err(i)) = inline_diagnostics
19289 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19290 inline_diagnostics.insert(
19291 i,
19292 (
19293 start_anchor,
19294 InlineDiagnostic {
19295 message,
19296 group_id: diagnostic_entry.diagnostic.group_id,
19297 start: diagnostic_entry.range.start.to_point(&snapshot),
19298 is_primary: diagnostic_entry.diagnostic.is_primary,
19299 severity: diagnostic_entry.diagnostic.severity,
19300 },
19301 ),
19302 );
19303 }
19304 inline_diagnostics
19305 })
19306 .await;
19307
19308 editor
19309 .update(cx, |editor, cx| {
19310 editor.inline_diagnostics = new_inline_diagnostics;
19311 cx.notify();
19312 })
19313 .ok();
19314 });
19315 }
19316
19317 fn pull_diagnostics(
19318 &mut self,
19319 buffer_id: BufferId,
19320 _window: &Window,
19321 cx: &mut Context<Self>,
19322 ) -> Option<()> {
19323 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
19324 return None;
19325 }
19326 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19327 .diagnostics
19328 .lsp_pull_diagnostics;
19329 if !pull_diagnostics_settings.enabled {
19330 return None;
19331 }
19332 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19333 let project = self.project()?.downgrade();
19334 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19335
19336 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19337 cx.background_executor().timer(debounce).await;
19338 if let Ok(task) = project.update(cx, |project, cx| {
19339 project.lsp_store().update(cx, |lsp_store, cx| {
19340 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19341 })
19342 }) {
19343 task.await.log_err();
19344 }
19345 project
19346 .update(cx, |project, cx| {
19347 project.lsp_store().update(cx, |lsp_store, cx| {
19348 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19349 })
19350 })
19351 .log_err();
19352 });
19353
19354 Some(())
19355 }
19356
19357 pub fn set_selections_from_remote(
19358 &mut self,
19359 selections: Vec<Selection<Anchor>>,
19360 pending_selection: Option<Selection<Anchor>>,
19361 window: &mut Window,
19362 cx: &mut Context<Self>,
19363 ) {
19364 let old_cursor_position = self.selections.newest_anchor().head();
19365 self.selections
19366 .change_with(&self.display_snapshot(cx), |s| {
19367 s.select_anchors(selections);
19368 if let Some(pending_selection) = pending_selection {
19369 s.set_pending(pending_selection, SelectMode::Character);
19370 } else {
19371 s.clear_pending();
19372 }
19373 });
19374 self.selections_did_change(
19375 false,
19376 &old_cursor_position,
19377 SelectionEffects::default(),
19378 window,
19379 cx,
19380 );
19381 }
19382
19383 pub fn transact(
19384 &mut self,
19385 window: &mut Window,
19386 cx: &mut Context<Self>,
19387 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19388 ) -> Option<TransactionId> {
19389 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19390 this.start_transaction_at(Instant::now(), window, cx);
19391 update(this, window, cx);
19392 this.end_transaction_at(Instant::now(), cx)
19393 })
19394 }
19395
19396 pub fn start_transaction_at(
19397 &mut self,
19398 now: Instant,
19399 window: &mut Window,
19400 cx: &mut Context<Self>,
19401 ) -> Option<TransactionId> {
19402 self.end_selection(window, cx);
19403 if let Some(tx_id) = self
19404 .buffer
19405 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19406 {
19407 self.selection_history
19408 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19409 cx.emit(EditorEvent::TransactionBegun {
19410 transaction_id: tx_id,
19411 });
19412 Some(tx_id)
19413 } else {
19414 None
19415 }
19416 }
19417
19418 pub fn end_transaction_at(
19419 &mut self,
19420 now: Instant,
19421 cx: &mut Context<Self>,
19422 ) -> Option<TransactionId> {
19423 if let Some(transaction_id) = self
19424 .buffer
19425 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19426 {
19427 if let Some((_, end_selections)) =
19428 self.selection_history.transaction_mut(transaction_id)
19429 {
19430 *end_selections = Some(self.selections.disjoint_anchors_arc());
19431 } else {
19432 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19433 }
19434
19435 cx.emit(EditorEvent::Edited { transaction_id });
19436 Some(transaction_id)
19437 } else {
19438 None
19439 }
19440 }
19441
19442 pub fn modify_transaction_selection_history(
19443 &mut self,
19444 transaction_id: TransactionId,
19445 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19446 ) -> bool {
19447 self.selection_history
19448 .transaction_mut(transaction_id)
19449 .map(modify)
19450 .is_some()
19451 }
19452
19453 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19454 if self.selection_mark_mode {
19455 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19456 s.move_with(|_, sel| {
19457 sel.collapse_to(sel.head(), SelectionGoal::None);
19458 });
19459 })
19460 }
19461 self.selection_mark_mode = true;
19462 cx.notify();
19463 }
19464
19465 pub fn swap_selection_ends(
19466 &mut self,
19467 _: &actions::SwapSelectionEnds,
19468 window: &mut Window,
19469 cx: &mut Context<Self>,
19470 ) {
19471 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19472 s.move_with(|_, sel| {
19473 if sel.start != sel.end {
19474 sel.reversed = !sel.reversed
19475 }
19476 });
19477 });
19478 self.request_autoscroll(Autoscroll::newest(), cx);
19479 cx.notify();
19480 }
19481
19482 pub fn toggle_focus(
19483 workspace: &mut Workspace,
19484 _: &actions::ToggleFocus,
19485 window: &mut Window,
19486 cx: &mut Context<Workspace>,
19487 ) {
19488 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19489 return;
19490 };
19491 workspace.activate_item(&item, true, true, window, cx);
19492 }
19493
19494 pub fn toggle_fold(
19495 &mut self,
19496 _: &actions::ToggleFold,
19497 window: &mut Window,
19498 cx: &mut Context<Self>,
19499 ) {
19500 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19501 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19502 let selection = self.selections.newest::<Point>(&display_map);
19503
19504 let range = if selection.is_empty() {
19505 let point = selection.head().to_display_point(&display_map);
19506 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19507 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19508 .to_point(&display_map);
19509 start..end
19510 } else {
19511 selection.range()
19512 };
19513 if display_map.folds_in_range(range).next().is_some() {
19514 self.unfold_lines(&Default::default(), window, cx)
19515 } else {
19516 self.fold(&Default::default(), window, cx)
19517 }
19518 } else {
19519 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19520 let buffer_ids: HashSet<_> = self
19521 .selections
19522 .disjoint_anchor_ranges()
19523 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19524 .collect();
19525
19526 let should_unfold = buffer_ids
19527 .iter()
19528 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19529
19530 for buffer_id in buffer_ids {
19531 if should_unfold {
19532 self.unfold_buffer(buffer_id, cx);
19533 } else {
19534 self.fold_buffer(buffer_id, cx);
19535 }
19536 }
19537 }
19538 }
19539
19540 pub fn toggle_fold_recursive(
19541 &mut self,
19542 _: &actions::ToggleFoldRecursive,
19543 window: &mut Window,
19544 cx: &mut Context<Self>,
19545 ) {
19546 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19547
19548 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19549 let range = if selection.is_empty() {
19550 let point = selection.head().to_display_point(&display_map);
19551 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19552 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19553 .to_point(&display_map);
19554 start..end
19555 } else {
19556 selection.range()
19557 };
19558 if display_map.folds_in_range(range).next().is_some() {
19559 self.unfold_recursive(&Default::default(), window, cx)
19560 } else {
19561 self.fold_recursive(&Default::default(), window, cx)
19562 }
19563 }
19564
19565 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19566 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19567 let mut to_fold = Vec::new();
19568 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19569 let selections = self.selections.all_adjusted(&display_map);
19570
19571 for selection in selections {
19572 let range = selection.range().sorted();
19573 let buffer_start_row = range.start.row;
19574
19575 if range.start.row != range.end.row {
19576 let mut found = false;
19577 let mut row = range.start.row;
19578 while row <= range.end.row {
19579 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19580 {
19581 found = true;
19582 row = crease.range().end.row + 1;
19583 to_fold.push(crease);
19584 } else {
19585 row += 1
19586 }
19587 }
19588 if found {
19589 continue;
19590 }
19591 }
19592
19593 for row in (0..=range.start.row).rev() {
19594 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19595 && crease.range().end.row >= buffer_start_row
19596 {
19597 to_fold.push(crease);
19598 if row <= range.start.row {
19599 break;
19600 }
19601 }
19602 }
19603 }
19604
19605 self.fold_creases(to_fold, true, window, cx);
19606 } else {
19607 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19608 let buffer_ids = self
19609 .selections
19610 .disjoint_anchor_ranges()
19611 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19612 .collect::<HashSet<_>>();
19613 for buffer_id in buffer_ids {
19614 self.fold_buffer(buffer_id, cx);
19615 }
19616 }
19617 }
19618
19619 pub fn toggle_fold_all(
19620 &mut self,
19621 _: &actions::ToggleFoldAll,
19622 window: &mut Window,
19623 cx: &mut Context<Self>,
19624 ) {
19625 let has_folds = if self.buffer.read(cx).is_singleton() {
19626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19627 let has_folds = display_map
19628 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19629 .next()
19630 .is_some();
19631 has_folds
19632 } else {
19633 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19634 let has_folds = buffer_ids
19635 .iter()
19636 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19637 has_folds
19638 };
19639
19640 if has_folds {
19641 self.unfold_all(&actions::UnfoldAll, window, cx);
19642 } else {
19643 self.fold_all(&actions::FoldAll, window, cx);
19644 }
19645 }
19646
19647 fn fold_at_level(
19648 &mut self,
19649 fold_at: &FoldAtLevel,
19650 window: &mut Window,
19651 cx: &mut Context<Self>,
19652 ) {
19653 if !self.buffer.read(cx).is_singleton() {
19654 return;
19655 }
19656
19657 let fold_at_level = fold_at.0;
19658 let snapshot = self.buffer.read(cx).snapshot(cx);
19659 let mut to_fold = Vec::new();
19660 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19661
19662 let row_ranges_to_keep: Vec<Range<u32>> = self
19663 .selections
19664 .all::<Point>(&self.display_snapshot(cx))
19665 .into_iter()
19666 .map(|sel| sel.start.row..sel.end.row)
19667 .collect();
19668
19669 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19670 while start_row < end_row {
19671 match self
19672 .snapshot(window, cx)
19673 .crease_for_buffer_row(MultiBufferRow(start_row))
19674 {
19675 Some(crease) => {
19676 let nested_start_row = crease.range().start.row + 1;
19677 let nested_end_row = crease.range().end.row;
19678
19679 if current_level < fold_at_level {
19680 stack.push((nested_start_row, nested_end_row, current_level + 1));
19681 } else if current_level == fold_at_level {
19682 // Fold iff there is no selection completely contained within the fold region
19683 if !row_ranges_to_keep.iter().any(|selection| {
19684 selection.end >= nested_start_row
19685 && selection.start <= nested_end_row
19686 }) {
19687 to_fold.push(crease);
19688 }
19689 }
19690
19691 start_row = nested_end_row + 1;
19692 }
19693 None => start_row += 1,
19694 }
19695 }
19696 }
19697
19698 self.fold_creases(to_fold, true, window, cx);
19699 }
19700
19701 pub fn fold_at_level_1(
19702 &mut self,
19703 _: &actions::FoldAtLevel1,
19704 window: &mut Window,
19705 cx: &mut Context<Self>,
19706 ) {
19707 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19708 }
19709
19710 pub fn fold_at_level_2(
19711 &mut self,
19712 _: &actions::FoldAtLevel2,
19713 window: &mut Window,
19714 cx: &mut Context<Self>,
19715 ) {
19716 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19717 }
19718
19719 pub fn fold_at_level_3(
19720 &mut self,
19721 _: &actions::FoldAtLevel3,
19722 window: &mut Window,
19723 cx: &mut Context<Self>,
19724 ) {
19725 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19726 }
19727
19728 pub fn fold_at_level_4(
19729 &mut self,
19730 _: &actions::FoldAtLevel4,
19731 window: &mut Window,
19732 cx: &mut Context<Self>,
19733 ) {
19734 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19735 }
19736
19737 pub fn fold_at_level_5(
19738 &mut self,
19739 _: &actions::FoldAtLevel5,
19740 window: &mut Window,
19741 cx: &mut Context<Self>,
19742 ) {
19743 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19744 }
19745
19746 pub fn fold_at_level_6(
19747 &mut self,
19748 _: &actions::FoldAtLevel6,
19749 window: &mut Window,
19750 cx: &mut Context<Self>,
19751 ) {
19752 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19753 }
19754
19755 pub fn fold_at_level_7(
19756 &mut self,
19757 _: &actions::FoldAtLevel7,
19758 window: &mut Window,
19759 cx: &mut Context<Self>,
19760 ) {
19761 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19762 }
19763
19764 pub fn fold_at_level_8(
19765 &mut self,
19766 _: &actions::FoldAtLevel8,
19767 window: &mut Window,
19768 cx: &mut Context<Self>,
19769 ) {
19770 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19771 }
19772
19773 pub fn fold_at_level_9(
19774 &mut self,
19775 _: &actions::FoldAtLevel9,
19776 window: &mut Window,
19777 cx: &mut Context<Self>,
19778 ) {
19779 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19780 }
19781
19782 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19783 if self.buffer.read(cx).is_singleton() {
19784 let mut fold_ranges = Vec::new();
19785 let snapshot = self.buffer.read(cx).snapshot(cx);
19786
19787 for row in 0..snapshot.max_row().0 {
19788 if let Some(foldable_range) = self
19789 .snapshot(window, cx)
19790 .crease_for_buffer_row(MultiBufferRow(row))
19791 {
19792 fold_ranges.push(foldable_range);
19793 }
19794 }
19795
19796 self.fold_creases(fold_ranges, true, window, cx);
19797 } else {
19798 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19799 editor
19800 .update_in(cx, |editor, _, cx| {
19801 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19802 editor.fold_buffer(buffer_id, cx);
19803 }
19804 })
19805 .ok();
19806 });
19807 }
19808 cx.emit(SearchEvent::ResultsCollapsedChanged(
19809 CollapseDirection::Collapsed,
19810 ));
19811 }
19812
19813 pub fn fold_function_bodies(
19814 &mut self,
19815 _: &actions::FoldFunctionBodies,
19816 window: &mut Window,
19817 cx: &mut Context<Self>,
19818 ) {
19819 let snapshot = self.buffer.read(cx).snapshot(cx);
19820
19821 let ranges = snapshot
19822 .text_object_ranges(
19823 MultiBufferOffset(0)..snapshot.len(),
19824 TreeSitterOptions::default(),
19825 )
19826 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19827 .collect::<Vec<_>>();
19828
19829 let creases = ranges
19830 .into_iter()
19831 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19832 .collect();
19833
19834 self.fold_creases(creases, true, window, cx);
19835 }
19836
19837 pub fn fold_recursive(
19838 &mut self,
19839 _: &actions::FoldRecursive,
19840 window: &mut Window,
19841 cx: &mut Context<Self>,
19842 ) {
19843 let mut to_fold = Vec::new();
19844 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19845 let selections = self.selections.all_adjusted(&display_map);
19846
19847 for selection in selections {
19848 let range = selection.range().sorted();
19849 let buffer_start_row = range.start.row;
19850
19851 if range.start.row != range.end.row {
19852 let mut found = false;
19853 for row in range.start.row..=range.end.row {
19854 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19855 found = true;
19856 to_fold.push(crease);
19857 }
19858 }
19859 if found {
19860 continue;
19861 }
19862 }
19863
19864 for row in (0..=range.start.row).rev() {
19865 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19866 if crease.range().end.row >= buffer_start_row {
19867 to_fold.push(crease);
19868 } else {
19869 break;
19870 }
19871 }
19872 }
19873 }
19874
19875 self.fold_creases(to_fold, true, window, cx);
19876 }
19877
19878 pub fn fold_at(
19879 &mut self,
19880 buffer_row: MultiBufferRow,
19881 window: &mut Window,
19882 cx: &mut Context<Self>,
19883 ) {
19884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19885
19886 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19887 let autoscroll = self
19888 .selections
19889 .all::<Point>(&display_map)
19890 .iter()
19891 .any(|selection| crease.range().overlaps(&selection.range()));
19892
19893 self.fold_creases(vec![crease], autoscroll, window, cx);
19894 }
19895 }
19896
19897 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19898 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19899 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19900 let buffer = display_map.buffer_snapshot();
19901 let selections = self.selections.all::<Point>(&display_map);
19902 let ranges = selections
19903 .iter()
19904 .map(|s| {
19905 let range = s.display_range(&display_map).sorted();
19906 let mut start = range.start.to_point(&display_map);
19907 let mut end = range.end.to_point(&display_map);
19908 start.column = 0;
19909 end.column = buffer.line_len(MultiBufferRow(end.row));
19910 start..end
19911 })
19912 .collect::<Vec<_>>();
19913
19914 self.unfold_ranges(&ranges, true, true, cx);
19915 } else {
19916 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19917 let buffer_ids = self
19918 .selections
19919 .disjoint_anchor_ranges()
19920 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19921 .collect::<HashSet<_>>();
19922 for buffer_id in buffer_ids {
19923 self.unfold_buffer(buffer_id, cx);
19924 }
19925 }
19926 }
19927
19928 pub fn unfold_recursive(
19929 &mut self,
19930 _: &UnfoldRecursive,
19931 _window: &mut Window,
19932 cx: &mut Context<Self>,
19933 ) {
19934 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19935 let selections = self.selections.all::<Point>(&display_map);
19936 let ranges = selections
19937 .iter()
19938 .map(|s| {
19939 let mut range = s.display_range(&display_map).sorted();
19940 *range.start.column_mut() = 0;
19941 *range.end.column_mut() = display_map.line_len(range.end.row());
19942 let start = range.start.to_point(&display_map);
19943 let end = range.end.to_point(&display_map);
19944 start..end
19945 })
19946 .collect::<Vec<_>>();
19947
19948 self.unfold_ranges(&ranges, true, true, cx);
19949 }
19950
19951 pub fn unfold_at(
19952 &mut self,
19953 buffer_row: MultiBufferRow,
19954 _window: &mut Window,
19955 cx: &mut Context<Self>,
19956 ) {
19957 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19958
19959 let intersection_range = Point::new(buffer_row.0, 0)
19960 ..Point::new(
19961 buffer_row.0,
19962 display_map.buffer_snapshot().line_len(buffer_row),
19963 );
19964
19965 let autoscroll = self
19966 .selections
19967 .all::<Point>(&display_map)
19968 .iter()
19969 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19970
19971 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19972 }
19973
19974 pub fn unfold_all(
19975 &mut self,
19976 _: &actions::UnfoldAll,
19977 _window: &mut Window,
19978 cx: &mut Context<Self>,
19979 ) {
19980 if self.buffer.read(cx).is_singleton() {
19981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19982 self.unfold_ranges(
19983 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19984 true,
19985 true,
19986 cx,
19987 );
19988 } else {
19989 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19990 editor
19991 .update(cx, |editor, cx| {
19992 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19993 editor.unfold_buffer(buffer_id, cx);
19994 }
19995 })
19996 .ok();
19997 });
19998 }
19999 cx.emit(SearchEvent::ResultsCollapsedChanged(
20000 CollapseDirection::Expanded,
20001 ));
20002 }
20003
20004 pub fn fold_selected_ranges(
20005 &mut self,
20006 _: &FoldSelectedRanges,
20007 window: &mut Window,
20008 cx: &mut Context<Self>,
20009 ) {
20010 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20011 let selections = self.selections.all_adjusted(&display_map);
20012 let ranges = selections
20013 .into_iter()
20014 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20015 .collect::<Vec<_>>();
20016 self.fold_creases(ranges, true, window, cx);
20017 }
20018
20019 pub fn fold_ranges<T: ToOffset + Clone>(
20020 &mut self,
20021 ranges: Vec<Range<T>>,
20022 auto_scroll: bool,
20023 window: &mut Window,
20024 cx: &mut Context<Self>,
20025 ) {
20026 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20027 let ranges = ranges
20028 .into_iter()
20029 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20030 .collect::<Vec<_>>();
20031 self.fold_creases(ranges, auto_scroll, window, cx);
20032 }
20033
20034 pub fn fold_creases<T: ToOffset + Clone>(
20035 &mut self,
20036 creases: Vec<Crease<T>>,
20037 auto_scroll: bool,
20038 _window: &mut Window,
20039 cx: &mut Context<Self>,
20040 ) {
20041 if creases.is_empty() {
20042 return;
20043 }
20044
20045 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20046
20047 if auto_scroll {
20048 self.request_autoscroll(Autoscroll::fit(), cx);
20049 }
20050
20051 cx.notify();
20052
20053 self.scrollbar_marker_state.dirty = true;
20054 self.folds_did_change(cx);
20055 }
20056
20057 /// Removes any folds whose ranges intersect any of the given ranges.
20058 pub fn unfold_ranges<T: ToOffset + Clone>(
20059 &mut self,
20060 ranges: &[Range<T>],
20061 inclusive: bool,
20062 auto_scroll: bool,
20063 cx: &mut Context<Self>,
20064 ) {
20065 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20066 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
20067 });
20068 self.folds_did_change(cx);
20069 }
20070
20071 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20072 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
20073 return;
20074 }
20075
20076 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20077 self.display_map.update(cx, |display_map, cx| {
20078 display_map.fold_buffers([buffer_id], cx)
20079 });
20080
20081 let snapshot = self.display_snapshot(cx);
20082 self.selections.change_with(&snapshot, |selections| {
20083 selections.remove_selections_from_buffer(buffer_id);
20084 });
20085
20086 cx.emit(EditorEvent::BufferFoldToggled {
20087 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
20088 folded: true,
20089 });
20090 cx.notify();
20091 }
20092
20093 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20094 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20095 return;
20096 }
20097 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20098 self.display_map.update(cx, |display_map, cx| {
20099 display_map.unfold_buffers([buffer_id], cx);
20100 });
20101 cx.emit(EditorEvent::BufferFoldToggled {
20102 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20103 folded: false,
20104 });
20105 cx.notify();
20106 }
20107
20108 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20109 self.display_map.read(cx).is_buffer_folded(buffer)
20110 }
20111
20112 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20113 self.display_map.read(cx).folded_buffers()
20114 }
20115
20116 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20117 self.display_map.update(cx, |display_map, cx| {
20118 display_map.disable_header_for_buffer(buffer_id, cx);
20119 });
20120 cx.notify();
20121 }
20122
20123 /// Removes any folds with the given ranges.
20124 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20125 &mut self,
20126 ranges: &[Range<T>],
20127 type_id: TypeId,
20128 auto_scroll: bool,
20129 cx: &mut Context<Self>,
20130 ) {
20131 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20132 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20133 });
20134 self.folds_did_change(cx);
20135 }
20136
20137 fn remove_folds_with<T: ToOffset + Clone>(
20138 &mut self,
20139 ranges: &[Range<T>],
20140 auto_scroll: bool,
20141 cx: &mut Context<Self>,
20142 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20143 ) {
20144 if ranges.is_empty() {
20145 return;
20146 }
20147
20148 let mut buffers_affected = HashSet::default();
20149 let multi_buffer = self.buffer().read(cx);
20150 for range in ranges {
20151 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20152 buffers_affected.insert(buffer.read(cx).remote_id());
20153 };
20154 }
20155
20156 self.display_map.update(cx, update);
20157
20158 if auto_scroll {
20159 self.request_autoscroll(Autoscroll::fit(), cx);
20160 }
20161
20162 cx.notify();
20163 self.scrollbar_marker_state.dirty = true;
20164 self.active_indent_guides_state.dirty = true;
20165 }
20166
20167 pub fn update_renderer_widths(
20168 &mut self,
20169 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20170 cx: &mut Context<Self>,
20171 ) -> bool {
20172 self.display_map
20173 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20174 }
20175
20176 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20177 self.display_map.read(cx).fold_placeholder.clone()
20178 }
20179
20180 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20181 self.buffer.update(cx, |buffer, cx| {
20182 buffer.set_all_diff_hunks_expanded(cx);
20183 });
20184 }
20185
20186 pub fn expand_all_diff_hunks(
20187 &mut self,
20188 _: &ExpandAllDiffHunks,
20189 _window: &mut Window,
20190 cx: &mut Context<Self>,
20191 ) {
20192 self.buffer.update(cx, |buffer, cx| {
20193 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20194 });
20195 }
20196
20197 pub fn collapse_all_diff_hunks(
20198 &mut self,
20199 _: &CollapseAllDiffHunks,
20200 _window: &mut Window,
20201 cx: &mut Context<Self>,
20202 ) {
20203 self.buffer.update(cx, |buffer, cx| {
20204 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20205 });
20206 }
20207
20208 pub fn toggle_selected_diff_hunks(
20209 &mut self,
20210 _: &ToggleSelectedDiffHunks,
20211 _window: &mut Window,
20212 cx: &mut Context<Self>,
20213 ) {
20214 let ranges: Vec<_> = self
20215 .selections
20216 .disjoint_anchors()
20217 .iter()
20218 .map(|s| s.range())
20219 .collect();
20220 self.toggle_diff_hunks_in_ranges(ranges, cx);
20221 }
20222
20223 pub fn diff_hunks_in_ranges<'a>(
20224 &'a self,
20225 ranges: &'a [Range<Anchor>],
20226 buffer: &'a MultiBufferSnapshot,
20227 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20228 ranges.iter().flat_map(move |range| {
20229 let end_excerpt_id = range.end.excerpt_id;
20230 let range = range.to_point(buffer);
20231 let mut peek_end = range.end;
20232 if range.end.row < buffer.max_row().0 {
20233 peek_end = Point::new(range.end.row + 1, 0);
20234 }
20235 buffer
20236 .diff_hunks_in_range(range.start..peek_end)
20237 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20238 })
20239 }
20240
20241 pub fn has_stageable_diff_hunks_in_ranges(
20242 &self,
20243 ranges: &[Range<Anchor>],
20244 snapshot: &MultiBufferSnapshot,
20245 ) -> bool {
20246 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20247 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20248 }
20249
20250 pub fn toggle_staged_selected_diff_hunks(
20251 &mut self,
20252 _: &::git::ToggleStaged,
20253 _: &mut Window,
20254 cx: &mut Context<Self>,
20255 ) {
20256 let snapshot = self.buffer.read(cx).snapshot(cx);
20257 let ranges: Vec<_> = self
20258 .selections
20259 .disjoint_anchors()
20260 .iter()
20261 .map(|s| s.range())
20262 .collect();
20263 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20264 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20265 }
20266
20267 pub fn set_render_diff_hunk_controls(
20268 &mut self,
20269 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20270 cx: &mut Context<Self>,
20271 ) {
20272 self.render_diff_hunk_controls = render_diff_hunk_controls;
20273 cx.notify();
20274 }
20275
20276 pub fn stage_and_next(
20277 &mut self,
20278 _: &::git::StageAndNext,
20279 window: &mut Window,
20280 cx: &mut Context<Self>,
20281 ) {
20282 self.do_stage_or_unstage_and_next(true, window, cx);
20283 }
20284
20285 pub fn unstage_and_next(
20286 &mut self,
20287 _: &::git::UnstageAndNext,
20288 window: &mut Window,
20289 cx: &mut Context<Self>,
20290 ) {
20291 self.do_stage_or_unstage_and_next(false, window, cx);
20292 }
20293
20294 pub fn stage_or_unstage_diff_hunks(
20295 &mut self,
20296 stage: bool,
20297 ranges: Vec<Range<Anchor>>,
20298 cx: &mut Context<Self>,
20299 ) {
20300 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20301 cx.spawn(async move |this, cx| {
20302 task.await?;
20303 this.update(cx, |this, cx| {
20304 let snapshot = this.buffer.read(cx).snapshot(cx);
20305 let chunk_by = this
20306 .diff_hunks_in_ranges(&ranges, &snapshot)
20307 .chunk_by(|hunk| hunk.buffer_id);
20308 for (buffer_id, hunks) in &chunk_by {
20309 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20310 }
20311 })
20312 })
20313 .detach_and_log_err(cx);
20314 }
20315
20316 fn save_buffers_for_ranges_if_needed(
20317 &mut self,
20318 ranges: &[Range<Anchor>],
20319 cx: &mut Context<Editor>,
20320 ) -> Task<Result<()>> {
20321 let multibuffer = self.buffer.read(cx);
20322 let snapshot = multibuffer.read(cx);
20323 let buffer_ids: HashSet<_> = ranges
20324 .iter()
20325 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20326 .collect();
20327 drop(snapshot);
20328
20329 let mut buffers = HashSet::default();
20330 for buffer_id in buffer_ids {
20331 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20332 let buffer = buffer_entity.read(cx);
20333 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20334 {
20335 buffers.insert(buffer_entity);
20336 }
20337 }
20338 }
20339
20340 if let Some(project) = &self.project {
20341 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20342 } else {
20343 Task::ready(Ok(()))
20344 }
20345 }
20346
20347 fn do_stage_or_unstage_and_next(
20348 &mut self,
20349 stage: bool,
20350 window: &mut Window,
20351 cx: &mut Context<Self>,
20352 ) {
20353 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20354
20355 if ranges.iter().any(|range| range.start != range.end) {
20356 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20357 return;
20358 }
20359
20360 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20361 let snapshot = self.snapshot(window, cx);
20362 let position = self
20363 .selections
20364 .newest::<Point>(&snapshot.display_snapshot)
20365 .head();
20366 let mut row = snapshot
20367 .buffer_snapshot()
20368 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20369 .find(|hunk| hunk.row_range.start.0 > position.row)
20370 .map(|hunk| hunk.row_range.start);
20371
20372 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20373 // Outside of the project diff editor, wrap around to the beginning.
20374 if !all_diff_hunks_expanded {
20375 row = row.or_else(|| {
20376 snapshot
20377 .buffer_snapshot()
20378 .diff_hunks_in_range(Point::zero()..position)
20379 .find(|hunk| hunk.row_range.end.0 < position.row)
20380 .map(|hunk| hunk.row_range.start)
20381 });
20382 }
20383
20384 if let Some(row) = row {
20385 let destination = Point::new(row.0, 0);
20386 let autoscroll = Autoscroll::center();
20387
20388 self.unfold_ranges(&[destination..destination], false, false, cx);
20389 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20390 s.select_ranges([destination..destination]);
20391 });
20392 }
20393 }
20394
20395 fn do_stage_or_unstage(
20396 &self,
20397 stage: bool,
20398 buffer_id: BufferId,
20399 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20400 cx: &mut App,
20401 ) -> Option<()> {
20402 let project = self.project()?;
20403 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20404 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20405 let buffer_snapshot = buffer.read(cx).snapshot();
20406 let file_exists = buffer_snapshot
20407 .file()
20408 .is_some_and(|file| file.disk_state().exists());
20409 diff.update(cx, |diff, cx| {
20410 diff.stage_or_unstage_hunks(
20411 stage,
20412 &hunks
20413 .map(|hunk| buffer_diff::DiffHunk {
20414 buffer_range: hunk.buffer_range,
20415 // We don't need to pass in word diffs here because they're only used for rendering and
20416 // this function changes internal state
20417 base_word_diffs: Vec::default(),
20418 buffer_word_diffs: Vec::default(),
20419 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20420 ..hunk.diff_base_byte_range.end.0,
20421 secondary_status: hunk.status.secondary,
20422 range: Point::zero()..Point::zero(), // unused
20423 })
20424 .collect::<Vec<_>>(),
20425 &buffer_snapshot,
20426 file_exists,
20427 cx,
20428 )
20429 });
20430 None
20431 }
20432
20433 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20434 let ranges: Vec<_> = self
20435 .selections
20436 .disjoint_anchors()
20437 .iter()
20438 .map(|s| s.range())
20439 .collect();
20440 self.buffer
20441 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20442 }
20443
20444 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20445 self.buffer.update(cx, |buffer, cx| {
20446 let ranges = vec![Anchor::min()..Anchor::max()];
20447 if !buffer.all_diff_hunks_expanded()
20448 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20449 {
20450 buffer.collapse_diff_hunks(ranges, cx);
20451 true
20452 } else {
20453 false
20454 }
20455 })
20456 }
20457
20458 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20459 if self.buffer.read(cx).all_diff_hunks_expanded() {
20460 return true;
20461 }
20462 let ranges = vec![Anchor::min()..Anchor::max()];
20463 self.buffer
20464 .read(cx)
20465 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20466 }
20467
20468 fn toggle_diff_hunks_in_ranges(
20469 &mut self,
20470 ranges: Vec<Range<Anchor>>,
20471 cx: &mut Context<Editor>,
20472 ) {
20473 self.buffer.update(cx, |buffer, cx| {
20474 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20475 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20476 })
20477 }
20478
20479 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20480 self.buffer.update(cx, |buffer, cx| {
20481 let snapshot = buffer.snapshot(cx);
20482 let excerpt_id = range.end.excerpt_id;
20483 let point_range = range.to_point(&snapshot);
20484 let expand = !buffer.single_hunk_is_expanded(range, cx);
20485 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20486 })
20487 }
20488
20489 pub(crate) fn apply_all_diff_hunks(
20490 &mut self,
20491 _: &ApplyAllDiffHunks,
20492 window: &mut Window,
20493 cx: &mut Context<Self>,
20494 ) {
20495 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20496
20497 let buffers = self.buffer.read(cx).all_buffers();
20498 for branch_buffer in buffers {
20499 branch_buffer.update(cx, |branch_buffer, cx| {
20500 branch_buffer.merge_into_base(Vec::new(), cx);
20501 });
20502 }
20503
20504 if let Some(project) = self.project.clone() {
20505 self.save(
20506 SaveOptions {
20507 format: true,
20508 autosave: false,
20509 },
20510 project,
20511 window,
20512 cx,
20513 )
20514 .detach_and_log_err(cx);
20515 }
20516 }
20517
20518 pub(crate) fn apply_selected_diff_hunks(
20519 &mut self,
20520 _: &ApplyDiffHunk,
20521 window: &mut Window,
20522 cx: &mut Context<Self>,
20523 ) {
20524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20525 let snapshot = self.snapshot(window, cx);
20526 let hunks = snapshot.hunks_for_ranges(
20527 self.selections
20528 .all(&snapshot.display_snapshot)
20529 .into_iter()
20530 .map(|selection| selection.range()),
20531 );
20532 let mut ranges_by_buffer = HashMap::default();
20533 self.transact(window, cx, |editor, _window, cx| {
20534 for hunk in hunks {
20535 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20536 ranges_by_buffer
20537 .entry(buffer.clone())
20538 .or_insert_with(Vec::new)
20539 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20540 }
20541 }
20542
20543 for (buffer, ranges) in ranges_by_buffer {
20544 buffer.update(cx, |buffer, cx| {
20545 buffer.merge_into_base(ranges, cx);
20546 });
20547 }
20548 });
20549
20550 if let Some(project) = self.project.clone() {
20551 self.save(
20552 SaveOptions {
20553 format: true,
20554 autosave: false,
20555 },
20556 project,
20557 window,
20558 cx,
20559 )
20560 .detach_and_log_err(cx);
20561 }
20562 }
20563
20564 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20565 if hovered != self.gutter_hovered {
20566 self.gutter_hovered = hovered;
20567 cx.notify();
20568 }
20569 }
20570
20571 pub fn insert_blocks(
20572 &mut self,
20573 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20574 autoscroll: Option<Autoscroll>,
20575 cx: &mut Context<Self>,
20576 ) -> Vec<CustomBlockId> {
20577 let blocks = self
20578 .display_map
20579 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20580 if let Some(autoscroll) = autoscroll {
20581 self.request_autoscroll(autoscroll, cx);
20582 }
20583 cx.notify();
20584 blocks
20585 }
20586
20587 pub fn resize_blocks(
20588 &mut self,
20589 heights: HashMap<CustomBlockId, u32>,
20590 autoscroll: Option<Autoscroll>,
20591 cx: &mut Context<Self>,
20592 ) {
20593 self.display_map
20594 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20595 if let Some(autoscroll) = autoscroll {
20596 self.request_autoscroll(autoscroll, cx);
20597 }
20598 cx.notify();
20599 }
20600
20601 pub fn replace_blocks(
20602 &mut self,
20603 renderers: HashMap<CustomBlockId, RenderBlock>,
20604 autoscroll: Option<Autoscroll>,
20605 cx: &mut Context<Self>,
20606 ) {
20607 self.display_map
20608 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20609 if let Some(autoscroll) = autoscroll {
20610 self.request_autoscroll(autoscroll, cx);
20611 }
20612 cx.notify();
20613 }
20614
20615 pub fn remove_blocks(
20616 &mut self,
20617 block_ids: HashSet<CustomBlockId>,
20618 autoscroll: Option<Autoscroll>,
20619 cx: &mut Context<Self>,
20620 ) {
20621 self.display_map.update(cx, |display_map, cx| {
20622 display_map.remove_blocks(block_ids, cx)
20623 });
20624 if let Some(autoscroll) = autoscroll {
20625 self.request_autoscroll(autoscroll, cx);
20626 }
20627 cx.notify();
20628 }
20629
20630 pub fn row_for_block(
20631 &self,
20632 block_id: CustomBlockId,
20633 cx: &mut Context<Self>,
20634 ) -> Option<DisplayRow> {
20635 self.display_map
20636 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20637 }
20638
20639 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20640 self.focused_block = Some(focused_block);
20641 }
20642
20643 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20644 self.focused_block.take()
20645 }
20646
20647 pub fn insert_creases(
20648 &mut self,
20649 creases: impl IntoIterator<Item = Crease<Anchor>>,
20650 cx: &mut Context<Self>,
20651 ) -> Vec<CreaseId> {
20652 self.display_map
20653 .update(cx, |map, cx| map.insert_creases(creases, cx))
20654 }
20655
20656 pub fn remove_creases(
20657 &mut self,
20658 ids: impl IntoIterator<Item = CreaseId>,
20659 cx: &mut Context<Self>,
20660 ) -> Vec<(CreaseId, Range<Anchor>)> {
20661 self.display_map
20662 .update(cx, |map, cx| map.remove_creases(ids, cx))
20663 }
20664
20665 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20666 self.display_map
20667 .update(cx, |map, cx| map.snapshot(cx))
20668 .longest_row()
20669 }
20670
20671 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20672 self.display_map
20673 .update(cx, |map, cx| map.snapshot(cx))
20674 .max_point()
20675 }
20676
20677 pub fn text(&self, cx: &App) -> String {
20678 self.buffer.read(cx).read(cx).text()
20679 }
20680
20681 pub fn is_empty(&self, cx: &App) -> bool {
20682 self.buffer.read(cx).read(cx).is_empty()
20683 }
20684
20685 pub fn text_option(&self, cx: &App) -> Option<String> {
20686 let text = self.text(cx);
20687 let text = text.trim();
20688
20689 if text.is_empty() {
20690 return None;
20691 }
20692
20693 Some(text.to_string())
20694 }
20695
20696 pub fn set_text(
20697 &mut self,
20698 text: impl Into<Arc<str>>,
20699 window: &mut Window,
20700 cx: &mut Context<Self>,
20701 ) {
20702 self.transact(window, cx, |this, _, cx| {
20703 this.buffer
20704 .read(cx)
20705 .as_singleton()
20706 .expect("you can only call set_text on editors for singleton buffers")
20707 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20708 });
20709 }
20710
20711 pub fn display_text(&self, cx: &mut App) -> String {
20712 self.display_map
20713 .update(cx, |map, cx| map.snapshot(cx))
20714 .text()
20715 }
20716
20717 fn create_minimap(
20718 &self,
20719 minimap_settings: MinimapSettings,
20720 window: &mut Window,
20721 cx: &mut Context<Self>,
20722 ) -> Option<Entity<Self>> {
20723 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20724 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20725 }
20726
20727 fn initialize_new_minimap(
20728 &self,
20729 minimap_settings: MinimapSettings,
20730 window: &mut Window,
20731 cx: &mut Context<Self>,
20732 ) -> Entity<Self> {
20733 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20734 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20735
20736 let mut minimap = Editor::new_internal(
20737 EditorMode::Minimap {
20738 parent: cx.weak_entity(),
20739 },
20740 self.buffer.clone(),
20741 None,
20742 Some(self.display_map.clone()),
20743 window,
20744 cx,
20745 );
20746 minimap.scroll_manager.clone_state(&self.scroll_manager);
20747 minimap.set_text_style_refinement(TextStyleRefinement {
20748 font_size: Some(MINIMAP_FONT_SIZE),
20749 font_weight: Some(MINIMAP_FONT_WEIGHT),
20750 font_family: Some(MINIMAP_FONT_FAMILY),
20751 ..Default::default()
20752 });
20753 minimap.update_minimap_configuration(minimap_settings, cx);
20754 cx.new(|_| minimap)
20755 }
20756
20757 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20758 let current_line_highlight = minimap_settings
20759 .current_line_highlight
20760 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20761 self.set_current_line_highlight(Some(current_line_highlight));
20762 }
20763
20764 pub fn minimap(&self) -> Option<&Entity<Self>> {
20765 self.minimap
20766 .as_ref()
20767 .filter(|_| self.minimap_visibility.visible())
20768 }
20769
20770 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20771 let mut wrap_guides = smallvec![];
20772
20773 if self.show_wrap_guides == Some(false) {
20774 return wrap_guides;
20775 }
20776
20777 let settings = self.buffer.read(cx).language_settings(cx);
20778 if settings.show_wrap_guides {
20779 match self.soft_wrap_mode(cx) {
20780 SoftWrap::Column(soft_wrap) => {
20781 wrap_guides.push((soft_wrap as usize, true));
20782 }
20783 SoftWrap::Bounded(soft_wrap) => {
20784 wrap_guides.push((soft_wrap as usize, true));
20785 }
20786 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20787 }
20788 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20789 }
20790
20791 wrap_guides
20792 }
20793
20794 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20795 let settings = self.buffer.read(cx).language_settings(cx);
20796 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20797 match mode {
20798 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20799 SoftWrap::None
20800 }
20801 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20802 language_settings::SoftWrap::PreferredLineLength => {
20803 SoftWrap::Column(settings.preferred_line_length)
20804 }
20805 language_settings::SoftWrap::Bounded => {
20806 SoftWrap::Bounded(settings.preferred_line_length)
20807 }
20808 }
20809 }
20810
20811 pub fn set_soft_wrap_mode(
20812 &mut self,
20813 mode: language_settings::SoftWrap,
20814 cx: &mut Context<Self>,
20815 ) {
20816 self.soft_wrap_mode_override = Some(mode);
20817 cx.notify();
20818 }
20819
20820 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20821 self.hard_wrap = hard_wrap;
20822 cx.notify();
20823 }
20824
20825 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20826 self.text_style_refinement = Some(style);
20827 }
20828
20829 /// called by the Element so we know what style we were most recently rendered with.
20830 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20831 // We intentionally do not inform the display map about the minimap style
20832 // so that wrapping is not recalculated and stays consistent for the editor
20833 // and its linked minimap.
20834 if !self.mode.is_minimap() {
20835 let font = style.text.font();
20836 let font_size = style.text.font_size.to_pixels(window.rem_size());
20837 let display_map = self
20838 .placeholder_display_map
20839 .as_ref()
20840 .filter(|_| self.is_empty(cx))
20841 .unwrap_or(&self.display_map);
20842
20843 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20844 }
20845 self.style = Some(style);
20846 }
20847
20848 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20849 if self.style.is_none() {
20850 self.style = Some(self.create_style(cx));
20851 }
20852 self.style.as_ref().unwrap()
20853 }
20854
20855 // Called by the element. This method is not designed to be called outside of the editor
20856 // element's layout code because it does not notify when rewrapping is computed synchronously.
20857 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20858 if self.is_empty(cx) {
20859 self.placeholder_display_map
20860 .as_ref()
20861 .map_or(false, |display_map| {
20862 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20863 })
20864 } else {
20865 self.display_map
20866 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20867 }
20868 }
20869
20870 pub fn set_soft_wrap(&mut self) {
20871 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20872 }
20873
20874 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20875 if self.soft_wrap_mode_override.is_some() {
20876 self.soft_wrap_mode_override.take();
20877 } else {
20878 let soft_wrap = match self.soft_wrap_mode(cx) {
20879 SoftWrap::GitDiff => return,
20880 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20881 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20882 language_settings::SoftWrap::None
20883 }
20884 };
20885 self.soft_wrap_mode_override = Some(soft_wrap);
20886 }
20887 cx.notify();
20888 }
20889
20890 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20891 let Some(workspace) = self.workspace() else {
20892 return;
20893 };
20894 let fs = workspace.read(cx).app_state().fs.clone();
20895 let current_show = TabBarSettings::get_global(cx).show;
20896 update_settings_file(fs, cx, move |setting, _| {
20897 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20898 });
20899 }
20900
20901 pub fn toggle_indent_guides(
20902 &mut self,
20903 _: &ToggleIndentGuides,
20904 _: &mut Window,
20905 cx: &mut Context<Self>,
20906 ) {
20907 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20908 self.buffer
20909 .read(cx)
20910 .language_settings(cx)
20911 .indent_guides
20912 .enabled
20913 });
20914 self.show_indent_guides = Some(!currently_enabled);
20915 cx.notify();
20916 }
20917
20918 fn should_show_indent_guides(&self) -> Option<bool> {
20919 self.show_indent_guides
20920 }
20921
20922 pub fn disable_indent_guides_for_buffer(
20923 &mut self,
20924 buffer_id: BufferId,
20925 cx: &mut Context<Self>,
20926 ) {
20927 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20928 cx.notify();
20929 }
20930
20931 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20932 self.buffers_with_disabled_indent_guides
20933 .contains(&buffer_id)
20934 }
20935
20936 pub fn toggle_line_numbers(
20937 &mut self,
20938 _: &ToggleLineNumbers,
20939 _: &mut Window,
20940 cx: &mut Context<Self>,
20941 ) {
20942 let mut editor_settings = EditorSettings::get_global(cx).clone();
20943 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20944 EditorSettings::override_global(editor_settings, cx);
20945 }
20946
20947 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20948 if let Some(show_line_numbers) = self.show_line_numbers {
20949 return show_line_numbers;
20950 }
20951 EditorSettings::get_global(cx).gutter.line_numbers
20952 }
20953
20954 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20955 match (
20956 self.use_relative_line_numbers,
20957 EditorSettings::get_global(cx).relative_line_numbers,
20958 ) {
20959 (None, setting) => setting,
20960 (Some(false), _) => RelativeLineNumbers::Disabled,
20961 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20962 (Some(true), _) => RelativeLineNumbers::Enabled,
20963 }
20964 }
20965
20966 pub fn toggle_relative_line_numbers(
20967 &mut self,
20968 _: &ToggleRelativeLineNumbers,
20969 _: &mut Window,
20970 cx: &mut Context<Self>,
20971 ) {
20972 let is_relative = self.relative_line_numbers(cx);
20973 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20974 }
20975
20976 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20977 self.use_relative_line_numbers = is_relative;
20978 cx.notify();
20979 }
20980
20981 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20982 self.show_gutter = show_gutter;
20983 cx.notify();
20984 }
20985
20986 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20987 self.show_scrollbars = ScrollbarAxes {
20988 horizontal: show,
20989 vertical: show,
20990 };
20991 cx.notify();
20992 }
20993
20994 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20995 self.show_scrollbars.vertical = show;
20996 cx.notify();
20997 }
20998
20999 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21000 self.show_scrollbars.horizontal = show;
21001 cx.notify();
21002 }
21003
21004 pub fn set_minimap_visibility(
21005 &mut self,
21006 minimap_visibility: MinimapVisibility,
21007 window: &mut Window,
21008 cx: &mut Context<Self>,
21009 ) {
21010 if self.minimap_visibility != minimap_visibility {
21011 if minimap_visibility.visible() && self.minimap.is_none() {
21012 let minimap_settings = EditorSettings::get_global(cx).minimap;
21013 self.minimap =
21014 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21015 }
21016 self.minimap_visibility = minimap_visibility;
21017 cx.notify();
21018 }
21019 }
21020
21021 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21022 self.set_show_scrollbars(false, cx);
21023 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21024 }
21025
21026 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21027 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21028 }
21029
21030 /// Normally the text in full mode and auto height editors is padded on the
21031 /// left side by roughly half a character width for improved hit testing.
21032 ///
21033 /// Use this method to disable this for cases where this is not wanted (e.g.
21034 /// if you want to align the editor text with some other text above or below)
21035 /// or if you want to add this padding to single-line editors.
21036 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21037 self.offset_content = offset_content;
21038 cx.notify();
21039 }
21040
21041 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21042 self.show_line_numbers = Some(show_line_numbers);
21043 cx.notify();
21044 }
21045
21046 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21047 self.disable_expand_excerpt_buttons = true;
21048 cx.notify();
21049 }
21050
21051 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21052 self.delegate_expand_excerpts = delegate;
21053 }
21054
21055 pub fn set_scroll_companion(&mut self, companion: Option<WeakEntity<Editor>>) {
21056 self.scroll_companion = companion;
21057 }
21058
21059 pub fn scroll_companion(&self) -> Option<&WeakEntity<Editor>> {
21060 self.scroll_companion.as_ref()
21061 }
21062
21063 pub fn set_on_local_selections_changed(
21064 &mut self,
21065 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21066 ) {
21067 self.on_local_selections_changed = callback;
21068 }
21069
21070 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21071 self.suppress_selection_callback = suppress;
21072 }
21073
21074 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21075 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21076 cx.notify();
21077 }
21078
21079 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21080 self.show_code_actions = Some(show_code_actions);
21081 cx.notify();
21082 }
21083
21084 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21085 self.show_runnables = Some(show_runnables);
21086 cx.notify();
21087 }
21088
21089 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21090 self.show_breakpoints = Some(show_breakpoints);
21091 cx.notify();
21092 }
21093
21094 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21095 self.show_diff_review_button = show;
21096 cx.notify();
21097 }
21098
21099 pub fn show_diff_review_button(&self) -> bool {
21100 self.show_diff_review_button
21101 }
21102
21103 pub fn render_diff_review_button(
21104 &self,
21105 display_row: DisplayRow,
21106 width: Pixels,
21107 cx: &mut Context<Self>,
21108 ) -> impl IntoElement {
21109 let text_color = cx.theme().colors().text;
21110 let icon_color = cx.theme().colors().icon_accent;
21111
21112 h_flex()
21113 .id("diff_review_button")
21114 .cursor_pointer()
21115 .w(width - px(1.))
21116 .h(relative(0.9))
21117 .justify_center()
21118 .rounded_sm()
21119 .border_1()
21120 .border_color(text_color.opacity(0.1))
21121 .bg(text_color.opacity(0.15))
21122 .hover(|s| {
21123 s.bg(icon_color.opacity(0.4))
21124 .border_color(icon_color.opacity(0.5))
21125 })
21126 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21127 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21128 .on_mouse_down(
21129 gpui::MouseButton::Left,
21130 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21131 editor.start_diff_review_drag(display_row, window, cx);
21132 }),
21133 )
21134 }
21135
21136 pub fn start_diff_review_drag(
21137 &mut self,
21138 display_row: DisplayRow,
21139 window: &mut Window,
21140 cx: &mut Context<Self>,
21141 ) {
21142 let snapshot = self.snapshot(window, cx);
21143 let point = snapshot
21144 .display_snapshot
21145 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21146 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21147 self.diff_review_drag_state = Some(DiffReviewDragState {
21148 start_anchor: anchor,
21149 current_anchor: anchor,
21150 });
21151 cx.notify();
21152 }
21153
21154 pub fn update_diff_review_drag(
21155 &mut self,
21156 display_row: DisplayRow,
21157 window: &mut Window,
21158 cx: &mut Context<Self>,
21159 ) {
21160 if self.diff_review_drag_state.is_none() {
21161 return;
21162 }
21163 let snapshot = self.snapshot(window, cx);
21164 let point = snapshot
21165 .display_snapshot
21166 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21167 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21168 if let Some(drag_state) = &mut self.diff_review_drag_state {
21169 drag_state.current_anchor = anchor;
21170 cx.notify();
21171 }
21172 }
21173
21174 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21175 if let Some(drag_state) = self.diff_review_drag_state.take() {
21176 let snapshot = self.snapshot(window, cx);
21177 let range = drag_state.row_range(&snapshot.display_snapshot);
21178 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21179 }
21180 cx.notify();
21181 }
21182
21183 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21184 self.diff_review_drag_state = None;
21185 cx.notify();
21186 }
21187
21188 /// Calculates the appropriate block height for the diff review overlay.
21189 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21190 /// and 2 lines per comment when expanded.
21191 fn calculate_overlay_height(
21192 &self,
21193 hunk_key: &DiffHunkKey,
21194 comments_expanded: bool,
21195 snapshot: &MultiBufferSnapshot,
21196 ) -> u32 {
21197 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21198 let base_height: u32 = 2; // Input row with avatar and buttons
21199
21200 if comment_count == 0 {
21201 base_height
21202 } else if comments_expanded {
21203 // Header (1 line) + 2 lines per comment
21204 base_height + 1 + (comment_count as u32 * 2)
21205 } else {
21206 // Just header when collapsed
21207 base_height + 1
21208 }
21209 }
21210
21211 pub fn show_diff_review_overlay(
21212 &mut self,
21213 display_range: Range<DisplayRow>,
21214 window: &mut Window,
21215 cx: &mut Context<Self>,
21216 ) {
21217 let Range { start, end } = display_range.sorted();
21218
21219 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21220 let editor_snapshot = self.snapshot(window, cx);
21221
21222 // Convert display rows to multibuffer points
21223 let start_point = editor_snapshot
21224 .display_snapshot
21225 .display_point_to_point(start.as_display_point(), Bias::Left);
21226 let end_point = editor_snapshot
21227 .display_snapshot
21228 .display_point_to_point(end.as_display_point(), Bias::Left);
21229 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21230
21231 // Create anchor range for the selected lines (start of first line to end of last line)
21232 let line_end = Point::new(
21233 end_point.row,
21234 buffer_snapshot.line_len(end_multi_buffer_row),
21235 );
21236 let anchor_range =
21237 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21238
21239 // Compute the hunk key for this display row
21240 let file_path = buffer_snapshot
21241 .file_at(start_point)
21242 .map(|file: &Arc<dyn language::File>| file.path().clone())
21243 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21244 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21245 let new_hunk_key = DiffHunkKey {
21246 file_path,
21247 hunk_start_anchor,
21248 };
21249
21250 // Check if we already have an overlay for this hunk
21251 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21252 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21253 }) {
21254 // Just focus the existing overlay's prompt editor
21255 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21256 window.focus(&focus_handle, cx);
21257 return;
21258 }
21259
21260 // Dismiss overlays that have no comments for their hunks
21261 self.dismiss_overlays_without_comments(cx);
21262
21263 // Get the current user's avatar URI from the project's user_store
21264 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21265 let user_store = project.read(cx).user_store();
21266 user_store
21267 .read(cx)
21268 .current_user()
21269 .map(|user| user.avatar_uri.clone())
21270 });
21271
21272 // Create anchor at the end of the last row so the block appears immediately below it
21273 // Use multibuffer coordinates for anchor creation
21274 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21275 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21276
21277 // Use the hunk key we already computed
21278 let hunk_key = new_hunk_key;
21279
21280 // Create the prompt editor for the review input
21281 let prompt_editor = cx.new(|cx| {
21282 let mut editor = Editor::single_line(window, cx);
21283 editor.set_placeholder_text("Add a review comment...", window, cx);
21284 editor
21285 });
21286
21287 // Register the Newline action on the prompt editor to submit the review
21288 let parent_editor = cx.entity().downgrade();
21289 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21290 prompt_editor.register_action({
21291 let parent_editor = parent_editor.clone();
21292 move |_: &crate::actions::Newline, window, cx| {
21293 if let Some(editor) = parent_editor.upgrade() {
21294 editor.update(cx, |editor, cx| {
21295 editor.submit_diff_review_comment(window, cx);
21296 });
21297 }
21298 }
21299 })
21300 });
21301
21302 // Calculate initial height based on existing comments for this hunk
21303 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21304
21305 // Create the overlay block
21306 let prompt_editor_for_render = prompt_editor.clone();
21307 let hunk_key_for_render = hunk_key.clone();
21308 let editor_handle = cx.entity().downgrade();
21309 let block = BlockProperties {
21310 style: BlockStyle::Sticky,
21311 placement: BlockPlacement::Below(anchor),
21312 height: Some(initial_height),
21313 render: Arc::new(move |cx| {
21314 Self::render_diff_review_overlay(
21315 &prompt_editor_for_render,
21316 &hunk_key_for_render,
21317 &editor_handle,
21318 cx,
21319 )
21320 }),
21321 priority: 0,
21322 };
21323
21324 let block_ids = self.insert_blocks([block], None, cx);
21325 let Some(block_id) = block_ids.into_iter().next() else {
21326 log::error!("Failed to insert diff review overlay block");
21327 return;
21328 };
21329
21330 self.diff_review_overlays.push(DiffReviewOverlay {
21331 anchor_range,
21332 block_id,
21333 prompt_editor: prompt_editor.clone(),
21334 hunk_key,
21335 comments_expanded: true,
21336 inline_edit_editors: HashMap::default(),
21337 inline_edit_subscriptions: HashMap::default(),
21338 user_avatar_uri,
21339 _subscription: subscription,
21340 });
21341
21342 // Focus the prompt editor
21343 let focus_handle = prompt_editor.focus_handle(cx);
21344 window.focus(&focus_handle, cx);
21345
21346 cx.notify();
21347 }
21348
21349 /// Dismisses all diff review overlays.
21350 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21351 if self.diff_review_overlays.is_empty() {
21352 return;
21353 }
21354 let block_ids: HashSet<_> = self
21355 .diff_review_overlays
21356 .drain(..)
21357 .map(|overlay| overlay.block_id)
21358 .collect();
21359 self.remove_blocks(block_ids, None, cx);
21360 cx.notify();
21361 }
21362
21363 /// Dismisses overlays that have no comments stored for their hunks.
21364 /// Keeps overlays that have at least one comment.
21365 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21366 let snapshot = self.buffer.read(cx).snapshot(cx);
21367
21368 // First, compute which overlays have comments (to avoid borrow issues with retain)
21369 let overlays_with_comments: Vec<bool> = self
21370 .diff_review_overlays
21371 .iter()
21372 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21373 .collect();
21374
21375 // Now collect block IDs to remove and retain overlays
21376 let mut block_ids_to_remove = HashSet::default();
21377 let mut index = 0;
21378 self.diff_review_overlays.retain(|overlay| {
21379 let has_comments = overlays_with_comments[index];
21380 index += 1;
21381 if !has_comments {
21382 block_ids_to_remove.insert(overlay.block_id);
21383 }
21384 has_comments
21385 });
21386
21387 if !block_ids_to_remove.is_empty() {
21388 self.remove_blocks(block_ids_to_remove, None, cx);
21389 cx.notify();
21390 }
21391 }
21392
21393 /// Refreshes the diff review overlay block to update its height and render function.
21394 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21395 fn refresh_diff_review_overlay_height(
21396 &mut self,
21397 hunk_key: &DiffHunkKey,
21398 _window: &mut Window,
21399 cx: &mut Context<Self>,
21400 ) {
21401 // Extract all needed data from overlay first to avoid borrow conflicts
21402 let snapshot = self.buffer.read(cx).snapshot(cx);
21403 let (comments_expanded, block_id, prompt_editor) = {
21404 let Some(overlay) = self
21405 .diff_review_overlays
21406 .iter()
21407 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21408 else {
21409 return;
21410 };
21411
21412 (
21413 overlay.comments_expanded,
21414 overlay.block_id,
21415 overlay.prompt_editor.clone(),
21416 )
21417 };
21418
21419 // Calculate new height
21420 let snapshot = self.buffer.read(cx).snapshot(cx);
21421 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21422
21423 // Update the block height using resize_blocks (avoids flicker)
21424 let mut heights = HashMap::default();
21425 heights.insert(block_id, new_height);
21426 self.resize_blocks(heights, None, cx);
21427
21428 // Update the render function using replace_blocks (avoids flicker)
21429 let hunk_key_for_render = hunk_key.clone();
21430 let editor_handle = cx.entity().downgrade();
21431 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21432 Arc::new(move |cx| {
21433 Self::render_diff_review_overlay(
21434 &prompt_editor,
21435 &hunk_key_for_render,
21436 &editor_handle,
21437 cx,
21438 )
21439 });
21440
21441 let mut renderers = HashMap::default();
21442 renderers.insert(block_id, render);
21443 self.replace_blocks(renderers, None, cx);
21444 }
21445
21446 /// Action handler for SubmitDiffReviewComment.
21447 pub fn submit_diff_review_comment_action(
21448 &mut self,
21449 _: &SubmitDiffReviewComment,
21450 window: &mut Window,
21451 cx: &mut Context<Self>,
21452 ) {
21453 self.submit_diff_review_comment(window, cx);
21454 }
21455
21456 /// Stores the diff review comment locally.
21457 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21458 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21459 // Find the overlay that currently has focus
21460 let overlay_index = self
21461 .diff_review_overlays
21462 .iter()
21463 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21464 let Some(overlay_index) = overlay_index else {
21465 return;
21466 };
21467 let overlay = &self.diff_review_overlays[overlay_index];
21468
21469 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21470 if comment_text.is_empty() {
21471 return;
21472 }
21473
21474 let anchor_range = overlay.anchor_range.clone();
21475 let hunk_key = overlay.hunk_key.clone();
21476
21477 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21478
21479 // Clear the prompt editor but keep the overlay open
21480 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21481 overlay.prompt_editor.update(cx, |editor, cx| {
21482 editor.clear(window, cx);
21483 });
21484 }
21485
21486 // Refresh the overlay to update the block height for the new comment
21487 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21488
21489 cx.notify();
21490 }
21491
21492 /// Returns the prompt editor for the diff review overlay, if one is active.
21493 /// This is primarily used for testing.
21494 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21495 self.diff_review_overlays
21496 .first()
21497 .map(|overlay| &overlay.prompt_editor)
21498 }
21499
21500 /// Returns the line range for the first diff review overlay, if one is active.
21501 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21502 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21503 let overlay = self.diff_review_overlays.first()?;
21504 let snapshot = self.buffer.read(cx).snapshot(cx);
21505 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21506 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21507 let start_row = snapshot
21508 .point_to_buffer_point(start_point)
21509 .map(|(_, p, _)| p.row)
21510 .unwrap_or(start_point.row);
21511 let end_row = snapshot
21512 .point_to_buffer_point(end_point)
21513 .map(|(_, p, _)| p.row)
21514 .unwrap_or(end_point.row);
21515 Some((start_row, end_row))
21516 }
21517
21518 /// Sets whether the comments section is expanded in the diff review overlay.
21519 /// This is primarily used for testing.
21520 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21521 for overlay in &mut self.diff_review_overlays {
21522 overlay.comments_expanded = expanded;
21523 }
21524 cx.notify();
21525 }
21526
21527 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21528 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21529 a.file_path == b.file_path
21530 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21531 }
21532
21533 /// Returns comments for a specific hunk, ordered by creation time.
21534 pub fn comments_for_hunk<'a>(
21535 &'a self,
21536 key: &DiffHunkKey,
21537 snapshot: &MultiBufferSnapshot,
21538 ) -> &'a [StoredReviewComment] {
21539 let key_point = key.hunk_start_anchor.to_point(snapshot);
21540 self.stored_review_comments
21541 .iter()
21542 .find(|(k, _)| {
21543 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21544 })
21545 .map(|(_, comments)| comments.as_slice())
21546 .unwrap_or(&[])
21547 }
21548
21549 /// Returns the total count of stored review comments across all hunks.
21550 pub fn total_review_comment_count(&self) -> usize {
21551 self.stored_review_comments
21552 .iter()
21553 .map(|(_, v)| v.len())
21554 .sum()
21555 }
21556
21557 /// Returns the count of comments for a specific hunk.
21558 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21559 let key_point = key.hunk_start_anchor.to_point(snapshot);
21560 self.stored_review_comments
21561 .iter()
21562 .find(|(k, _)| {
21563 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21564 })
21565 .map(|(_, v)| v.len())
21566 .unwrap_or(0)
21567 }
21568
21569 /// Adds a new review comment to a specific hunk.
21570 pub fn add_review_comment(
21571 &mut self,
21572 hunk_key: DiffHunkKey,
21573 comment: String,
21574 anchor_range: Range<Anchor>,
21575 cx: &mut Context<Self>,
21576 ) -> usize {
21577 let id = self.next_review_comment_id;
21578 self.next_review_comment_id += 1;
21579
21580 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21581
21582 let snapshot = self.buffer.read(cx).snapshot(cx);
21583 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21584
21585 // Find existing entry for this hunk or add a new one
21586 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21587 k.file_path == hunk_key.file_path
21588 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21589 }) {
21590 comments.push(stored_comment);
21591 } else {
21592 self.stored_review_comments
21593 .push((hunk_key, vec![stored_comment]));
21594 }
21595
21596 cx.emit(EditorEvent::ReviewCommentsChanged {
21597 total_count: self.total_review_comment_count(),
21598 });
21599 cx.notify();
21600 id
21601 }
21602
21603 /// Removes a review comment by ID from any hunk.
21604 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21605 for (_, comments) in self.stored_review_comments.iter_mut() {
21606 if let Some(index) = comments.iter().position(|c| c.id == id) {
21607 comments.remove(index);
21608 cx.emit(EditorEvent::ReviewCommentsChanged {
21609 total_count: self.total_review_comment_count(),
21610 });
21611 cx.notify();
21612 return true;
21613 }
21614 }
21615 false
21616 }
21617
21618 /// Updates a review comment's text by ID.
21619 pub fn update_review_comment(
21620 &mut self,
21621 id: usize,
21622 new_comment: String,
21623 cx: &mut Context<Self>,
21624 ) -> bool {
21625 for (_, comments) in self.stored_review_comments.iter_mut() {
21626 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21627 comment.comment = new_comment;
21628 comment.is_editing = false;
21629 cx.emit(EditorEvent::ReviewCommentsChanged {
21630 total_count: self.total_review_comment_count(),
21631 });
21632 cx.notify();
21633 return true;
21634 }
21635 }
21636 false
21637 }
21638
21639 /// Sets a comment's editing state.
21640 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21641 for (_, comments) in self.stored_review_comments.iter_mut() {
21642 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21643 comment.is_editing = is_editing;
21644 cx.notify();
21645 return;
21646 }
21647 }
21648 }
21649
21650 /// Takes all stored comments from all hunks, clearing the storage.
21651 /// Returns a Vec of (hunk_key, comments) pairs.
21652 pub fn take_all_review_comments(
21653 &mut self,
21654 cx: &mut Context<Self>,
21655 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21656 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21657 self.dismiss_all_diff_review_overlays(cx);
21658 let comments = std::mem::take(&mut self.stored_review_comments);
21659 // Reset the ID counter since all comments have been taken
21660 self.next_review_comment_id = 0;
21661 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21662 cx.notify();
21663 comments
21664 }
21665
21666 /// Removes review comments whose anchors are no longer valid or whose
21667 /// associated diff hunks no longer exist.
21668 ///
21669 /// This should be called when the buffer changes to prevent orphaned comments
21670 /// from accumulating.
21671 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21672 let snapshot = self.buffer.read(cx).snapshot(cx);
21673 let original_count = self.total_review_comment_count();
21674
21675 // Remove comments with invalid hunk anchors
21676 self.stored_review_comments
21677 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21678
21679 // Also clean up individual comments with invalid anchor ranges
21680 for (_, comments) in &mut self.stored_review_comments {
21681 comments.retain(|comment| {
21682 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
21683 });
21684 }
21685
21686 // Remove empty hunk entries
21687 self.stored_review_comments
21688 .retain(|(_, comments)| !comments.is_empty());
21689
21690 let new_count = self.total_review_comment_count();
21691 if new_count != original_count {
21692 cx.emit(EditorEvent::ReviewCommentsChanged {
21693 total_count: new_count,
21694 });
21695 cx.notify();
21696 }
21697 }
21698
21699 /// Toggles the expanded state of the comments section in the overlay.
21700 pub fn toggle_review_comments_expanded(
21701 &mut self,
21702 _: &ToggleReviewCommentsExpanded,
21703 window: &mut Window,
21704 cx: &mut Context<Self>,
21705 ) {
21706 // Find the overlay that currently has focus, or use the first one
21707 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21708 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21709 overlay.comments_expanded = !overlay.comments_expanded;
21710 Some(overlay.hunk_key.clone())
21711 } else {
21712 None
21713 }
21714 });
21715
21716 // If no focused overlay found, toggle the first one
21717 let hunk_key = overlay_info.or_else(|| {
21718 self.diff_review_overlays.first_mut().map(|overlay| {
21719 overlay.comments_expanded = !overlay.comments_expanded;
21720 overlay.hunk_key.clone()
21721 })
21722 });
21723
21724 if let Some(hunk_key) = hunk_key {
21725 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21726 cx.notify();
21727 }
21728 }
21729
21730 /// Handles the EditReviewComment action - sets a comment into editing mode.
21731 pub fn edit_review_comment(
21732 &mut self,
21733 action: &EditReviewComment,
21734 window: &mut Window,
21735 cx: &mut Context<Self>,
21736 ) {
21737 let comment_id = action.id;
21738
21739 // Set the comment to editing mode
21740 self.set_comment_editing(comment_id, true, cx);
21741
21742 // Find the overlay that contains this comment and create an inline editor if needed
21743 // First, find which hunk this comment belongs to
21744 let hunk_key = self
21745 .stored_review_comments
21746 .iter()
21747 .find_map(|(key, comments)| {
21748 if comments.iter().any(|c| c.id == comment_id) {
21749 Some(key.clone())
21750 } else {
21751 None
21752 }
21753 });
21754
21755 let snapshot = self.buffer.read(cx).snapshot(cx);
21756 if let Some(hunk_key) = hunk_key {
21757 if let Some(overlay) = self
21758 .diff_review_overlays
21759 .iter_mut()
21760 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21761 {
21762 if let std::collections::hash_map::Entry::Vacant(entry) =
21763 overlay.inline_edit_editors.entry(comment_id)
21764 {
21765 // Find the comment text
21766 let comment_text = self
21767 .stored_review_comments
21768 .iter()
21769 .flat_map(|(_, comments)| comments)
21770 .find(|c| c.id == comment_id)
21771 .map(|c| c.comment.clone())
21772 .unwrap_or_default();
21773
21774 // Create inline editor
21775 let parent_editor = cx.entity().downgrade();
21776 let inline_editor = cx.new(|cx| {
21777 let mut editor = Editor::single_line(window, cx);
21778 editor.set_text(&*comment_text, window, cx);
21779 // Select all text for easy replacement
21780 editor.select_all(&crate::actions::SelectAll, window, cx);
21781 editor
21782 });
21783
21784 // Register the Newline action to confirm the edit
21785 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
21786 inline_editor.register_action({
21787 let parent_editor = parent_editor.clone();
21788 move |_: &crate::actions::Newline, window, cx| {
21789 if let Some(editor) = parent_editor.upgrade() {
21790 editor.update(cx, |editor, cx| {
21791 editor.confirm_edit_review_comment(comment_id, window, cx);
21792 });
21793 }
21794 }
21795 })
21796 });
21797
21798 // Store the subscription to keep the action handler alive
21799 overlay
21800 .inline_edit_subscriptions
21801 .insert(comment_id, subscription);
21802
21803 // Focus the inline editor
21804 let focus_handle = inline_editor.focus_handle(cx);
21805 window.focus(&focus_handle, cx);
21806
21807 entry.insert(inline_editor);
21808 }
21809 }
21810 }
21811
21812 cx.notify();
21813 }
21814
21815 /// Confirms an inline edit of a review comment.
21816 pub fn confirm_edit_review_comment(
21817 &mut self,
21818 comment_id: usize,
21819 _window: &mut Window,
21820 cx: &mut Context<Self>,
21821 ) {
21822 // Get the new text from the inline editor
21823 // Find the overlay containing this comment's inline editor
21824 let snapshot = self.buffer.read(cx).snapshot(cx);
21825 let hunk_key = self
21826 .stored_review_comments
21827 .iter()
21828 .find_map(|(key, comments)| {
21829 if comments.iter().any(|c| c.id == comment_id) {
21830 Some(key.clone())
21831 } else {
21832 None
21833 }
21834 });
21835
21836 let new_text = hunk_key
21837 .as_ref()
21838 .and_then(|hunk_key| {
21839 self.diff_review_overlays
21840 .iter()
21841 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21842 })
21843 .as_ref()
21844 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
21845 .map(|editor| editor.read(cx).text(cx).trim().to_string());
21846
21847 if let Some(new_text) = new_text {
21848 if !new_text.is_empty() {
21849 self.update_review_comment(comment_id, new_text, cx);
21850 }
21851 }
21852
21853 // Remove the inline editor and its subscription
21854 if let Some(hunk_key) = hunk_key {
21855 if let Some(overlay) = self
21856 .diff_review_overlays
21857 .iter_mut()
21858 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21859 {
21860 overlay.inline_edit_editors.remove(&comment_id);
21861 overlay.inline_edit_subscriptions.remove(&comment_id);
21862 }
21863 }
21864
21865 // Clear editing state
21866 self.set_comment_editing(comment_id, false, cx);
21867 }
21868
21869 /// Cancels an inline edit of a review comment.
21870 pub fn cancel_edit_review_comment(
21871 &mut self,
21872 comment_id: usize,
21873 _window: &mut Window,
21874 cx: &mut Context<Self>,
21875 ) {
21876 // Find which hunk this comment belongs to
21877 let hunk_key = self
21878 .stored_review_comments
21879 .iter()
21880 .find_map(|(key, comments)| {
21881 if comments.iter().any(|c| c.id == comment_id) {
21882 Some(key.clone())
21883 } else {
21884 None
21885 }
21886 });
21887
21888 // Remove the inline editor and its subscription
21889 if let Some(hunk_key) = hunk_key {
21890 let snapshot = self.buffer.read(cx).snapshot(cx);
21891 if let Some(overlay) = self
21892 .diff_review_overlays
21893 .iter_mut()
21894 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21895 {
21896 overlay.inline_edit_editors.remove(&comment_id);
21897 overlay.inline_edit_subscriptions.remove(&comment_id);
21898 }
21899 }
21900
21901 // Clear editing state
21902 self.set_comment_editing(comment_id, false, cx);
21903 }
21904
21905 /// Action handler for ConfirmEditReviewComment.
21906 pub fn confirm_edit_review_comment_action(
21907 &mut self,
21908 action: &ConfirmEditReviewComment,
21909 window: &mut Window,
21910 cx: &mut Context<Self>,
21911 ) {
21912 self.confirm_edit_review_comment(action.id, window, cx);
21913 }
21914
21915 /// Action handler for CancelEditReviewComment.
21916 pub fn cancel_edit_review_comment_action(
21917 &mut self,
21918 action: &CancelEditReviewComment,
21919 window: &mut Window,
21920 cx: &mut Context<Self>,
21921 ) {
21922 self.cancel_edit_review_comment(action.id, window, cx);
21923 }
21924
21925 /// Handles the DeleteReviewComment action - removes a comment.
21926 pub fn delete_review_comment(
21927 &mut self,
21928 action: &DeleteReviewComment,
21929 window: &mut Window,
21930 cx: &mut Context<Self>,
21931 ) {
21932 // Get the hunk key before removing the comment
21933 // Find the hunk key from the comment itself
21934 let comment_id = action.id;
21935 let hunk_key = self
21936 .stored_review_comments
21937 .iter()
21938 .find_map(|(key, comments)| {
21939 if comments.iter().any(|c| c.id == comment_id) {
21940 Some(key.clone())
21941 } else {
21942 None
21943 }
21944 });
21945
21946 // Also get it from the overlay for refresh purposes
21947 let overlay_hunk_key = self
21948 .diff_review_overlays
21949 .first()
21950 .map(|o| o.hunk_key.clone());
21951
21952 self.remove_review_comment(action.id, cx);
21953
21954 // Refresh the overlay height after removing a comment
21955 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
21956 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21957 }
21958 }
21959
21960 fn render_diff_review_overlay(
21961 prompt_editor: &Entity<Editor>,
21962 hunk_key: &DiffHunkKey,
21963 editor_handle: &WeakEntity<Editor>,
21964 cx: &mut BlockContext,
21965 ) -> AnyElement {
21966 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
21967 if ranges.is_empty() {
21968 return None;
21969 }
21970 let formatted: Vec<String> = ranges
21971 .iter()
21972 .map(|(start, end)| {
21973 let start_line = start + 1;
21974 let end_line = end + 1;
21975 if start_line == end_line {
21976 format!("Line {start_line}")
21977 } else {
21978 format!("Lines {start_line}-{end_line}")
21979 }
21980 })
21981 .collect();
21982 // Don't show label for single line in single excerpt
21983 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
21984 return None;
21985 }
21986 Some(formatted.join(" ⋯ "))
21987 }
21988
21989 let theme = cx.theme();
21990 let colors = theme.colors();
21991
21992 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
21993 editor_handle
21994 .upgrade()
21995 .map(|editor| {
21996 let editor = editor.read(cx);
21997 let snapshot = editor.buffer().read(cx).snapshot(cx);
21998 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
21999 let (expanded, editors, avatar_uri, line_ranges) = editor
22000 .diff_review_overlays
22001 .iter()
22002 .find(|overlay| {
22003 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22004 })
22005 .map(|o| {
22006 let start_point = o.anchor_range.start.to_point(&snapshot);
22007 let end_point = o.anchor_range.end.to_point(&snapshot);
22008 // Get line ranges per excerpt to detect discontinuities
22009 let buffer_ranges =
22010 snapshot.range_to_buffer_ranges(start_point..end_point);
22011 let ranges: Vec<(u32, u32)> = buffer_ranges
22012 .iter()
22013 .map(|(buffer, range, _)| {
22014 let start = buffer.offset_to_point(range.start.0).row;
22015 let end = buffer.offset_to_point(range.end.0).row;
22016 (start, end)
22017 })
22018 .collect();
22019 (
22020 o.comments_expanded,
22021 o.inline_edit_editors.clone(),
22022 o.user_avatar_uri.clone(),
22023 if ranges.is_empty() {
22024 None
22025 } else {
22026 Some(ranges)
22027 },
22028 )
22029 })
22030 .unwrap_or((true, HashMap::default(), None, None));
22031 (comments, expanded, editors, avatar_uri, line_ranges)
22032 })
22033 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22034
22035 let comment_count = comments.len();
22036 let avatar_size = px(20.);
22037 let action_icon_size = IconSize::XSmall;
22038
22039 v_flex()
22040 .w_full()
22041 .bg(colors.editor_background)
22042 .border_b_1()
22043 .border_color(colors.border)
22044 .px_2()
22045 .pb_2()
22046 .gap_2()
22047 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22048 .when_some(line_ranges, |el, ranges| {
22049 let label = format_line_ranges(&ranges);
22050 if let Some(label) = label {
22051 el.child(
22052 h_flex()
22053 .w_full()
22054 .px_2()
22055 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22056 )
22057 } else {
22058 el
22059 }
22060 })
22061 // Top row: editable input with user's avatar
22062 .child(
22063 h_flex()
22064 .w_full()
22065 .items_center()
22066 .gap_2()
22067 .px_2()
22068 .py_1p5()
22069 .rounded_md()
22070 .bg(colors.surface_background)
22071 .child(
22072 div()
22073 .size(avatar_size)
22074 .flex_shrink_0()
22075 .rounded_full()
22076 .overflow_hidden()
22077 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22078 Avatar::new(avatar_uri.clone())
22079 .size(avatar_size)
22080 .into_any_element()
22081 } else {
22082 Icon::new(IconName::Person)
22083 .size(IconSize::Small)
22084 .color(ui::Color::Muted)
22085 .into_any_element()
22086 }),
22087 )
22088 .child(
22089 div()
22090 .flex_1()
22091 .border_1()
22092 .border_color(colors.border)
22093 .rounded_md()
22094 .bg(colors.editor_background)
22095 .px_2()
22096 .py_1()
22097 .child(prompt_editor.clone()),
22098 )
22099 .child(
22100 h_flex()
22101 .flex_shrink_0()
22102 .gap_1()
22103 .child(
22104 IconButton::new("diff-review-close", IconName::Close)
22105 .icon_color(ui::Color::Muted)
22106 .icon_size(action_icon_size)
22107 .tooltip(Tooltip::text("Close"))
22108 .on_click(|_, window, cx| {
22109 window
22110 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22111 }),
22112 )
22113 .child(
22114 IconButton::new("diff-review-add", IconName::Return)
22115 .icon_color(ui::Color::Muted)
22116 .icon_size(action_icon_size)
22117 .tooltip(Tooltip::text("Add comment"))
22118 .on_click(|_, window, cx| {
22119 window.dispatch_action(
22120 Box::new(crate::actions::SubmitDiffReviewComment),
22121 cx,
22122 );
22123 }),
22124 ),
22125 ),
22126 )
22127 // Expandable comments section (only shown when there are comments)
22128 .when(comment_count > 0, |el| {
22129 el.child(Self::render_comments_section(
22130 comments,
22131 comments_expanded,
22132 inline_editors,
22133 user_avatar_uri,
22134 avatar_size,
22135 action_icon_size,
22136 colors,
22137 ))
22138 })
22139 .into_any_element()
22140 }
22141
22142 fn render_comments_section(
22143 comments: Vec<StoredReviewComment>,
22144 expanded: bool,
22145 inline_editors: HashMap<usize, Entity<Editor>>,
22146 user_avatar_uri: Option<SharedUri>,
22147 avatar_size: Pixels,
22148 action_icon_size: IconSize,
22149 colors: &theme::ThemeColors,
22150 ) -> impl IntoElement {
22151 let comment_count = comments.len();
22152
22153 v_flex()
22154 .w_full()
22155 .gap_1()
22156 // Header with expand/collapse toggle
22157 .child(
22158 h_flex()
22159 .id("review-comments-header")
22160 .w_full()
22161 .items_center()
22162 .gap_1()
22163 .px_2()
22164 .py_1()
22165 .cursor_pointer()
22166 .rounded_md()
22167 .hover(|style| style.bg(colors.ghost_element_hover))
22168 .on_click(|_, window: &mut Window, cx| {
22169 window.dispatch_action(
22170 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22171 cx,
22172 );
22173 })
22174 .child(
22175 Icon::new(if expanded {
22176 IconName::ChevronDown
22177 } else {
22178 IconName::ChevronRight
22179 })
22180 .size(IconSize::Small)
22181 .color(ui::Color::Muted),
22182 )
22183 .child(
22184 Label::new(format!(
22185 "{} Comment{}",
22186 comment_count,
22187 if comment_count == 1 { "" } else { "s" }
22188 ))
22189 .size(LabelSize::Small)
22190 .color(Color::Muted),
22191 ),
22192 )
22193 // Comments list (when expanded)
22194 .when(expanded, |el| {
22195 el.children(comments.into_iter().map(|comment| {
22196 let inline_editor = inline_editors.get(&comment.id).cloned();
22197 Self::render_comment_row(
22198 comment,
22199 inline_editor,
22200 user_avatar_uri.clone(),
22201 avatar_size,
22202 action_icon_size,
22203 colors,
22204 )
22205 }))
22206 })
22207 }
22208
22209 fn render_comment_row(
22210 comment: StoredReviewComment,
22211 inline_editor: Option<Entity<Editor>>,
22212 user_avatar_uri: Option<SharedUri>,
22213 avatar_size: Pixels,
22214 action_icon_size: IconSize,
22215 colors: &theme::ThemeColors,
22216 ) -> impl IntoElement {
22217 let comment_id = comment.id;
22218 let is_editing = inline_editor.is_some();
22219
22220 h_flex()
22221 .w_full()
22222 .items_center()
22223 .gap_2()
22224 .px_2()
22225 .py_1p5()
22226 .rounded_md()
22227 .bg(colors.surface_background)
22228 .child(
22229 div()
22230 .size(avatar_size)
22231 .flex_shrink_0()
22232 .rounded_full()
22233 .overflow_hidden()
22234 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22235 Avatar::new(avatar_uri.clone())
22236 .size(avatar_size)
22237 .into_any_element()
22238 } else {
22239 Icon::new(IconName::Person)
22240 .size(IconSize::Small)
22241 .color(ui::Color::Muted)
22242 .into_any_element()
22243 }),
22244 )
22245 .child(if let Some(editor) = inline_editor {
22246 // Inline edit mode: show an editable text field
22247 div()
22248 .flex_1()
22249 .border_1()
22250 .border_color(colors.border)
22251 .rounded_md()
22252 .bg(colors.editor_background)
22253 .px_2()
22254 .py_1()
22255 .child(editor)
22256 .into_any_element()
22257 } else {
22258 // Display mode: show the comment text
22259 div()
22260 .flex_1()
22261 .text_sm()
22262 .text_color(colors.text)
22263 .child(comment.comment)
22264 .into_any_element()
22265 })
22266 .child(if is_editing {
22267 // Editing mode: show close and confirm buttons
22268 h_flex()
22269 .gap_1()
22270 .child(
22271 IconButton::new(
22272 format!("diff-review-cancel-edit-{comment_id}"),
22273 IconName::Close,
22274 )
22275 .icon_color(ui::Color::Muted)
22276 .icon_size(action_icon_size)
22277 .tooltip(Tooltip::text("Cancel"))
22278 .on_click(move |_, window, cx| {
22279 window.dispatch_action(
22280 Box::new(crate::actions::CancelEditReviewComment {
22281 id: comment_id,
22282 }),
22283 cx,
22284 );
22285 }),
22286 )
22287 .child(
22288 IconButton::new(
22289 format!("diff-review-confirm-edit-{comment_id}"),
22290 IconName::Return,
22291 )
22292 .icon_color(ui::Color::Muted)
22293 .icon_size(action_icon_size)
22294 .tooltip(Tooltip::text("Confirm"))
22295 .on_click(move |_, window, cx| {
22296 window.dispatch_action(
22297 Box::new(crate::actions::ConfirmEditReviewComment {
22298 id: comment_id,
22299 }),
22300 cx,
22301 );
22302 }),
22303 )
22304 .into_any_element()
22305 } else {
22306 // Display mode: no action buttons for now (edit/delete not yet implemented)
22307 gpui::Empty.into_any_element()
22308 })
22309 }
22310
22311 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22312 if self.display_map.read(cx).masked != masked {
22313 self.display_map.update(cx, |map, _| map.masked = masked);
22314 }
22315 cx.notify()
22316 }
22317
22318 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22319 self.show_wrap_guides = Some(show_wrap_guides);
22320 cx.notify();
22321 }
22322
22323 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22324 self.show_indent_guides = Some(show_indent_guides);
22325 cx.notify();
22326 }
22327
22328 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22329 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22330 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22331 && let Some(dir) = file.abs_path(cx).parent()
22332 {
22333 return Some(dir.to_owned());
22334 }
22335 }
22336
22337 None
22338 }
22339
22340 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22341 self.active_excerpt(cx)?
22342 .1
22343 .read(cx)
22344 .file()
22345 .and_then(|f| f.as_local())
22346 }
22347
22348 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22349 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22350 let buffer = buffer.read(cx);
22351 if let Some(project_path) = buffer.project_path(cx) {
22352 let project = self.project()?.read(cx);
22353 project.absolute_path(&project_path, cx)
22354 } else {
22355 buffer
22356 .file()
22357 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22358 }
22359 })
22360 }
22361
22362 pub fn reveal_in_finder(
22363 &mut self,
22364 _: &RevealInFileManager,
22365 _window: &mut Window,
22366 cx: &mut Context<Self>,
22367 ) {
22368 if let Some(path) = self.target_file_abs_path(cx) {
22369 if let Some(project) = self.project() {
22370 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22371 } else {
22372 cx.reveal_path(&path);
22373 }
22374 }
22375 }
22376
22377 pub fn copy_path(
22378 &mut self,
22379 _: &zed_actions::workspace::CopyPath,
22380 _window: &mut Window,
22381 cx: &mut Context<Self>,
22382 ) {
22383 if let Some(path) = self.target_file_abs_path(cx)
22384 && let Some(path) = path.to_str()
22385 {
22386 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22387 } else {
22388 cx.propagate();
22389 }
22390 }
22391
22392 pub fn copy_relative_path(
22393 &mut self,
22394 _: &zed_actions::workspace::CopyRelativePath,
22395 _window: &mut Window,
22396 cx: &mut Context<Self>,
22397 ) {
22398 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22399 let project = self.project()?.read(cx);
22400 let path = buffer.read(cx).file()?.path();
22401 let path = path.display(project.path_style(cx));
22402 Some(path)
22403 }) {
22404 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22405 } else {
22406 cx.propagate();
22407 }
22408 }
22409
22410 /// Returns the project path for the editor's buffer, if any buffer is
22411 /// opened in the editor.
22412 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22413 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22414 buffer.read(cx).project_path(cx)
22415 } else {
22416 None
22417 }
22418 }
22419
22420 // Returns true if the editor handled a go-to-line request
22421 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22422 maybe!({
22423 let breakpoint_store = self.breakpoint_store.as_ref()?;
22424
22425 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22426 else {
22427 self.clear_row_highlights::<ActiveDebugLine>();
22428 return None;
22429 };
22430
22431 let position = active_stack_frame.position;
22432 let buffer_id = position.buffer_id?;
22433 let snapshot = self
22434 .project
22435 .as_ref()?
22436 .read(cx)
22437 .buffer_for_id(buffer_id, cx)?
22438 .read(cx)
22439 .snapshot();
22440
22441 let mut handled = false;
22442 for (id, ExcerptRange { context, .. }) in
22443 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22444 {
22445 if context.start.cmp(&position, &snapshot).is_ge()
22446 || context.end.cmp(&position, &snapshot).is_lt()
22447 {
22448 continue;
22449 }
22450 let snapshot = self.buffer.read(cx).snapshot(cx);
22451 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22452
22453 handled = true;
22454 self.clear_row_highlights::<ActiveDebugLine>();
22455
22456 self.go_to_line::<ActiveDebugLine>(
22457 multibuffer_anchor,
22458 Some(cx.theme().colors().editor_debugger_active_line_background),
22459 window,
22460 cx,
22461 );
22462
22463 cx.notify();
22464 }
22465
22466 handled.then_some(())
22467 })
22468 .is_some()
22469 }
22470
22471 pub fn copy_file_name_without_extension(
22472 &mut self,
22473 _: &CopyFileNameWithoutExtension,
22474 _: &mut Window,
22475 cx: &mut Context<Self>,
22476 ) {
22477 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22478 let file = buffer.read(cx).file()?;
22479 file.path().file_stem()
22480 }) {
22481 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22482 }
22483 }
22484
22485 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22486 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22487 let file = buffer.read(cx).file()?;
22488 Some(file.file_name(cx))
22489 }) {
22490 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22491 }
22492 }
22493
22494 pub fn toggle_git_blame(
22495 &mut self,
22496 _: &::git::Blame,
22497 window: &mut Window,
22498 cx: &mut Context<Self>,
22499 ) {
22500 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22501
22502 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22503 self.start_git_blame(true, window, cx);
22504 }
22505
22506 cx.notify();
22507 }
22508
22509 pub fn toggle_git_blame_inline(
22510 &mut self,
22511 _: &ToggleGitBlameInline,
22512 window: &mut Window,
22513 cx: &mut Context<Self>,
22514 ) {
22515 self.toggle_git_blame_inline_internal(true, window, cx);
22516 cx.notify();
22517 }
22518
22519 pub fn open_git_blame_commit(
22520 &mut self,
22521 _: &OpenGitBlameCommit,
22522 window: &mut Window,
22523 cx: &mut Context<Self>,
22524 ) {
22525 self.open_git_blame_commit_internal(window, cx);
22526 }
22527
22528 fn open_git_blame_commit_internal(
22529 &mut self,
22530 window: &mut Window,
22531 cx: &mut Context<Self>,
22532 ) -> Option<()> {
22533 let blame = self.blame.as_ref()?;
22534 let snapshot = self.snapshot(window, cx);
22535 let cursor = self
22536 .selections
22537 .newest::<Point>(&snapshot.display_snapshot)
22538 .head();
22539 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22540 let (_, blame_entry) = blame
22541 .update(cx, |blame, cx| {
22542 blame
22543 .blame_for_rows(
22544 &[RowInfo {
22545 buffer_id: Some(buffer.remote_id()),
22546 buffer_row: Some(point.row),
22547 ..Default::default()
22548 }],
22549 cx,
22550 )
22551 .next()
22552 })
22553 .flatten()?;
22554 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22555 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22556 let workspace = self.workspace()?.downgrade();
22557 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22558 None
22559 }
22560
22561 pub fn git_blame_inline_enabled(&self) -> bool {
22562 self.git_blame_inline_enabled
22563 }
22564
22565 pub fn toggle_selection_menu(
22566 &mut self,
22567 _: &ToggleSelectionMenu,
22568 _: &mut Window,
22569 cx: &mut Context<Self>,
22570 ) {
22571 self.show_selection_menu = self
22572 .show_selection_menu
22573 .map(|show_selections_menu| !show_selections_menu)
22574 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22575
22576 cx.notify();
22577 }
22578
22579 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22580 self.show_selection_menu
22581 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22582 }
22583
22584 fn start_git_blame(
22585 &mut self,
22586 user_triggered: bool,
22587 window: &mut Window,
22588 cx: &mut Context<Self>,
22589 ) {
22590 if let Some(project) = self.project() {
22591 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22592 && buffer.read(cx).file().is_none()
22593 {
22594 return;
22595 }
22596
22597 let focused = self.focus_handle(cx).contains_focused(window, cx);
22598
22599 let project = project.clone();
22600 let blame = cx
22601 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22602 self.blame_subscription =
22603 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22604 self.blame = Some(blame);
22605 }
22606 }
22607
22608 fn toggle_git_blame_inline_internal(
22609 &mut self,
22610 user_triggered: bool,
22611 window: &mut Window,
22612 cx: &mut Context<Self>,
22613 ) {
22614 if self.git_blame_inline_enabled {
22615 self.git_blame_inline_enabled = false;
22616 self.show_git_blame_inline = false;
22617 self.show_git_blame_inline_delay_task.take();
22618 } else {
22619 self.git_blame_inline_enabled = true;
22620 self.start_git_blame_inline(user_triggered, window, cx);
22621 }
22622
22623 cx.notify();
22624 }
22625
22626 fn start_git_blame_inline(
22627 &mut self,
22628 user_triggered: bool,
22629 window: &mut Window,
22630 cx: &mut Context<Self>,
22631 ) {
22632 self.start_git_blame(user_triggered, window, cx);
22633
22634 if ProjectSettings::get_global(cx)
22635 .git
22636 .inline_blame_delay()
22637 .is_some()
22638 {
22639 self.start_inline_blame_timer(window, cx);
22640 } else {
22641 self.show_git_blame_inline = true
22642 }
22643 }
22644
22645 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22646 self.blame.as_ref()
22647 }
22648
22649 pub fn show_git_blame_gutter(&self) -> bool {
22650 self.show_git_blame_gutter
22651 }
22652
22653 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22654 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22655 }
22656
22657 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22658 self.show_git_blame_inline
22659 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22660 && !self.newest_selection_head_on_empty_line(cx)
22661 && self.has_blame_entries(cx)
22662 }
22663
22664 fn has_blame_entries(&self, cx: &App) -> bool {
22665 self.blame()
22666 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22667 }
22668
22669 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22670 let cursor_anchor = self.selections.newest_anchor().head();
22671
22672 let snapshot = self.buffer.read(cx).snapshot(cx);
22673 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22674
22675 snapshot.line_len(buffer_row) == 0
22676 }
22677
22678 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22679 let buffer_and_selection = maybe!({
22680 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22681 let selection_range = selection.range();
22682
22683 let multi_buffer = self.buffer().read(cx);
22684 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22685 let buffer_ranges = multi_buffer_snapshot
22686 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
22687
22688 let (buffer, range, _) = if selection.reversed {
22689 buffer_ranges.first()
22690 } else {
22691 buffer_ranges.last()
22692 }?;
22693
22694 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
22695 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
22696
22697 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22698 let selection = start_row_in_buffer..end_row_in_buffer;
22699
22700 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
22701 };
22702
22703 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22704 let (mut translated, _, _) = buffer_diff_snapshot.points_to_base_text_points(
22705 [
22706 Point::new(start_row_in_buffer, 0),
22707 Point::new(end_row_in_buffer, 0),
22708 ],
22709 buffer,
22710 );
22711 let start_row = translated.next().unwrap().start.row;
22712 let end_row = translated.next().unwrap().end.row;
22713
22714 Some((
22715 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22716 start_row..end_row,
22717 ))
22718 });
22719
22720 let Some((buffer, selection)) = buffer_and_selection else {
22721 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22722 };
22723
22724 let Some(project) = self.project() else {
22725 return Task::ready(Err(anyhow!("editor does not have project")));
22726 };
22727
22728 project.update(cx, |project, cx| {
22729 project.get_permalink_to_line(&buffer, selection, cx)
22730 })
22731 }
22732
22733 pub fn copy_permalink_to_line(
22734 &mut self,
22735 _: &CopyPermalinkToLine,
22736 window: &mut Window,
22737 cx: &mut Context<Self>,
22738 ) {
22739 let permalink_task = self.get_permalink_to_line(cx);
22740 let workspace = self.workspace();
22741
22742 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22743 Ok(permalink) => {
22744 cx.update(|_, cx| {
22745 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22746 })
22747 .ok();
22748 }
22749 Err(err) => {
22750 let message = format!("Failed to copy permalink: {err}");
22751
22752 anyhow::Result::<()>::Err(err).log_err();
22753
22754 if let Some(workspace) = workspace {
22755 workspace
22756 .update_in(cx, |workspace, _, cx| {
22757 struct CopyPermalinkToLine;
22758
22759 workspace.show_toast(
22760 Toast::new(
22761 NotificationId::unique::<CopyPermalinkToLine>(),
22762 message,
22763 ),
22764 cx,
22765 )
22766 })
22767 .ok();
22768 }
22769 }
22770 })
22771 .detach();
22772 }
22773
22774 pub fn copy_file_location(
22775 &mut self,
22776 _: &CopyFileLocation,
22777 _: &mut Window,
22778 cx: &mut Context<Self>,
22779 ) {
22780 let selection = self
22781 .selections
22782 .newest::<Point>(&self.display_snapshot(cx))
22783 .start
22784 .row
22785 + 1;
22786 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22787 let project = self.project()?.read(cx);
22788 let file = buffer.read(cx).file()?;
22789 let path = file.path().display(project.path_style(cx));
22790
22791 Some(format!("{path}:{selection}"))
22792 }) {
22793 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
22794 }
22795 }
22796
22797 pub fn open_permalink_to_line(
22798 &mut self,
22799 _: &OpenPermalinkToLine,
22800 window: &mut Window,
22801 cx: &mut Context<Self>,
22802 ) {
22803 let permalink_task = self.get_permalink_to_line(cx);
22804 let workspace = self.workspace();
22805
22806 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22807 Ok(permalink) => {
22808 cx.update(|_, cx| {
22809 cx.open_url(permalink.as_ref());
22810 })
22811 .ok();
22812 }
22813 Err(err) => {
22814 let message = format!("Failed to open permalink: {err}");
22815
22816 anyhow::Result::<()>::Err(err).log_err();
22817
22818 if let Some(workspace) = workspace {
22819 workspace.update(cx, |workspace, cx| {
22820 struct OpenPermalinkToLine;
22821
22822 workspace.show_toast(
22823 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
22824 cx,
22825 )
22826 });
22827 }
22828 }
22829 })
22830 .detach();
22831 }
22832
22833 pub fn insert_uuid_v4(
22834 &mut self,
22835 _: &InsertUuidV4,
22836 window: &mut Window,
22837 cx: &mut Context<Self>,
22838 ) {
22839 self.insert_uuid(UuidVersion::V4, window, cx);
22840 }
22841
22842 pub fn insert_uuid_v7(
22843 &mut self,
22844 _: &InsertUuidV7,
22845 window: &mut Window,
22846 cx: &mut Context<Self>,
22847 ) {
22848 self.insert_uuid(UuidVersion::V7, window, cx);
22849 }
22850
22851 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
22852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
22853 self.transact(window, cx, |this, window, cx| {
22854 let edits = this
22855 .selections
22856 .all::<Point>(&this.display_snapshot(cx))
22857 .into_iter()
22858 .map(|selection| {
22859 let uuid = match version {
22860 UuidVersion::V4 => uuid::Uuid::new_v4(),
22861 UuidVersion::V7 => uuid::Uuid::now_v7(),
22862 };
22863
22864 (selection.range(), uuid.to_string())
22865 });
22866 this.edit(edits, cx);
22867 this.refresh_edit_prediction(true, false, window, cx);
22868 });
22869 }
22870
22871 pub fn open_selections_in_multibuffer(
22872 &mut self,
22873 _: &OpenSelectionsInMultibuffer,
22874 window: &mut Window,
22875 cx: &mut Context<Self>,
22876 ) {
22877 let multibuffer = self.buffer.read(cx);
22878
22879 let Some(buffer) = multibuffer.as_singleton() else {
22880 return;
22881 };
22882
22883 let Some(workspace) = self.workspace() else {
22884 return;
22885 };
22886
22887 let title = multibuffer.title(cx).to_string();
22888
22889 let locations = self
22890 .selections
22891 .all_anchors(&self.display_snapshot(cx))
22892 .iter()
22893 .map(|selection| {
22894 (
22895 buffer.clone(),
22896 (selection.start.text_anchor..selection.end.text_anchor)
22897 .to_point(buffer.read(cx)),
22898 )
22899 })
22900 .into_group_map();
22901
22902 cx.spawn_in(window, async move |_, cx| {
22903 workspace.update_in(cx, |workspace, window, cx| {
22904 Self::open_locations_in_multibuffer(
22905 workspace,
22906 locations,
22907 format!("Selections for '{title}'"),
22908 false,
22909 false,
22910 MultibufferSelectionMode::All,
22911 window,
22912 cx,
22913 );
22914 })
22915 })
22916 .detach();
22917 }
22918
22919 /// Adds a row highlight for the given range. If a row has multiple highlights, the
22920 /// last highlight added will be used.
22921 ///
22922 /// If the range ends at the beginning of a line, then that line will not be highlighted.
22923 pub fn highlight_rows<T: 'static>(
22924 &mut self,
22925 range: Range<Anchor>,
22926 color: Hsla,
22927 options: RowHighlightOptions,
22928 cx: &mut Context<Self>,
22929 ) {
22930 let snapshot = self.buffer().read(cx).snapshot(cx);
22931 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
22932 let ix = row_highlights.binary_search_by(|highlight| {
22933 Ordering::Equal
22934 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
22935 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
22936 });
22937
22938 if let Err(mut ix) = ix {
22939 let index = post_inc(&mut self.highlight_order);
22940
22941 // If this range intersects with the preceding highlight, then merge it with
22942 // the preceding highlight. Otherwise insert a new highlight.
22943 let mut merged = false;
22944 if ix > 0 {
22945 let prev_highlight = &mut row_highlights[ix - 1];
22946 if prev_highlight
22947 .range
22948 .end
22949 .cmp(&range.start, &snapshot)
22950 .is_ge()
22951 {
22952 ix -= 1;
22953 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
22954 prev_highlight.range.end = range.end;
22955 }
22956 merged = true;
22957 prev_highlight.index = index;
22958 prev_highlight.color = color;
22959 prev_highlight.options = options;
22960 }
22961 }
22962
22963 if !merged {
22964 row_highlights.insert(
22965 ix,
22966 RowHighlight {
22967 range,
22968 index,
22969 color,
22970 options,
22971 type_id: TypeId::of::<T>(),
22972 },
22973 );
22974 }
22975
22976 // If any of the following highlights intersect with this one, merge them.
22977 while let Some(next_highlight) = row_highlights.get(ix + 1) {
22978 let highlight = &row_highlights[ix];
22979 if next_highlight
22980 .range
22981 .start
22982 .cmp(&highlight.range.end, &snapshot)
22983 .is_le()
22984 {
22985 if next_highlight
22986 .range
22987 .end
22988 .cmp(&highlight.range.end, &snapshot)
22989 .is_gt()
22990 {
22991 row_highlights[ix].range.end = next_highlight.range.end;
22992 }
22993 row_highlights.remove(ix + 1);
22994 } else {
22995 break;
22996 }
22997 }
22998 }
22999 }
23000
23001 /// Remove any highlighted row ranges of the given type that intersect the
23002 /// given ranges.
23003 pub fn remove_highlighted_rows<T: 'static>(
23004 &mut self,
23005 ranges_to_remove: Vec<Range<Anchor>>,
23006 cx: &mut Context<Self>,
23007 ) {
23008 let snapshot = self.buffer().read(cx).snapshot(cx);
23009 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23010 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23011 row_highlights.retain(|highlight| {
23012 while let Some(range_to_remove) = ranges_to_remove.peek() {
23013 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23014 Ordering::Less | Ordering::Equal => {
23015 ranges_to_remove.next();
23016 }
23017 Ordering::Greater => {
23018 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23019 Ordering::Less | Ordering::Equal => {
23020 return false;
23021 }
23022 Ordering::Greater => break,
23023 }
23024 }
23025 }
23026 }
23027
23028 true
23029 })
23030 }
23031
23032 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23033 pub fn clear_row_highlights<T: 'static>(&mut self) {
23034 self.highlighted_rows.remove(&TypeId::of::<T>());
23035 }
23036
23037 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23038 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23039 self.highlighted_rows
23040 .get(&TypeId::of::<T>())
23041 .map_or(&[] as &[_], |vec| vec.as_slice())
23042 .iter()
23043 .map(|highlight| (highlight.range.clone(), highlight.color))
23044 }
23045
23046 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23047 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23048 /// Allows to ignore certain kinds of highlights.
23049 pub fn highlighted_display_rows(
23050 &self,
23051 window: &mut Window,
23052 cx: &mut App,
23053 ) -> BTreeMap<DisplayRow, LineHighlight> {
23054 let snapshot = self.snapshot(window, cx);
23055 let mut used_highlight_orders = HashMap::default();
23056 self.highlighted_rows
23057 .iter()
23058 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23059 .fold(
23060 BTreeMap::<DisplayRow, LineHighlight>::new(),
23061 |mut unique_rows, highlight| {
23062 let start = highlight.range.start.to_display_point(&snapshot);
23063 let end = highlight.range.end.to_display_point(&snapshot);
23064 let start_row = start.row().0;
23065 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23066 {
23067 end.row().0.saturating_sub(1)
23068 } else {
23069 end.row().0
23070 };
23071 for row in start_row..=end_row {
23072 let used_index =
23073 used_highlight_orders.entry(row).or_insert(highlight.index);
23074 if highlight.index >= *used_index {
23075 *used_index = highlight.index;
23076 unique_rows.insert(
23077 DisplayRow(row),
23078 LineHighlight {
23079 include_gutter: highlight.options.include_gutter,
23080 border: None,
23081 background: highlight.color.into(),
23082 type_id: Some(highlight.type_id),
23083 },
23084 );
23085 }
23086 }
23087 unique_rows
23088 },
23089 )
23090 }
23091
23092 pub fn highlighted_display_row_for_autoscroll(
23093 &self,
23094 snapshot: &DisplaySnapshot,
23095 ) -> Option<DisplayRow> {
23096 self.highlighted_rows
23097 .values()
23098 .flat_map(|highlighted_rows| highlighted_rows.iter())
23099 .filter_map(|highlight| {
23100 if highlight.options.autoscroll {
23101 Some(highlight.range.start.to_display_point(snapshot).row())
23102 } else {
23103 None
23104 }
23105 })
23106 .min()
23107 }
23108
23109 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23110 self.highlight_background::<SearchWithinRange>(
23111 ranges,
23112 |_, colors| colors.colors().editor_document_highlight_read_background,
23113 cx,
23114 )
23115 }
23116
23117 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23118 self.breadcrumb_header = Some(new_header);
23119 }
23120
23121 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23122 self.clear_background_highlights::<SearchWithinRange>(cx);
23123 }
23124
23125 pub fn highlight_background<T: 'static>(
23126 &mut self,
23127 ranges: &[Range<Anchor>],
23128 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23129 cx: &mut Context<Self>,
23130 ) {
23131 self.background_highlights.insert(
23132 HighlightKey::Type(TypeId::of::<T>()),
23133 (Arc::new(color_fetcher), Arc::from(ranges)),
23134 );
23135 self.scrollbar_marker_state.dirty = true;
23136 cx.notify();
23137 }
23138
23139 pub fn highlight_background_key<T: 'static>(
23140 &mut self,
23141 key: usize,
23142 ranges: &[Range<Anchor>],
23143 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23144 cx: &mut Context<Self>,
23145 ) {
23146 self.background_highlights.insert(
23147 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23148 (Arc::new(color_fetcher), Arc::from(ranges)),
23149 );
23150 self.scrollbar_marker_state.dirty = true;
23151 cx.notify();
23152 }
23153
23154 pub fn clear_background_highlights<T: 'static>(
23155 &mut self,
23156 cx: &mut Context<Self>,
23157 ) -> Option<BackgroundHighlight> {
23158 let text_highlights = self
23159 .background_highlights
23160 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
23161 if !text_highlights.1.is_empty() {
23162 self.scrollbar_marker_state.dirty = true;
23163 cx.notify();
23164 }
23165 Some(text_highlights)
23166 }
23167
23168 pub fn clear_background_highlights_key<T: 'static>(
23169 &mut self,
23170 key: usize,
23171 cx: &mut Context<Self>,
23172 ) -> Option<BackgroundHighlight> {
23173 let text_highlights = self
23174 .background_highlights
23175 .remove(&HighlightKey::TypePlus(TypeId::of::<T>(), key))?;
23176 if !text_highlights.1.is_empty() {
23177 self.scrollbar_marker_state.dirty = true;
23178 cx.notify();
23179 }
23180 Some(text_highlights)
23181 }
23182
23183 pub fn highlight_gutter<T: 'static>(
23184 &mut self,
23185 ranges: impl Into<Vec<Range<Anchor>>>,
23186 color_fetcher: fn(&App) -> Hsla,
23187 cx: &mut Context<Self>,
23188 ) {
23189 self.gutter_highlights
23190 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23191 cx.notify();
23192 }
23193
23194 pub fn clear_gutter_highlights<T: 'static>(
23195 &mut self,
23196 cx: &mut Context<Self>,
23197 ) -> Option<GutterHighlight> {
23198 cx.notify();
23199 self.gutter_highlights.remove(&TypeId::of::<T>())
23200 }
23201
23202 pub fn insert_gutter_highlight<T: 'static>(
23203 &mut self,
23204 range: Range<Anchor>,
23205 color_fetcher: fn(&App) -> Hsla,
23206 cx: &mut Context<Self>,
23207 ) {
23208 let snapshot = self.buffer().read(cx).snapshot(cx);
23209 let mut highlights = self
23210 .gutter_highlights
23211 .remove(&TypeId::of::<T>())
23212 .map(|(_, highlights)| highlights)
23213 .unwrap_or_default();
23214 let ix = highlights.binary_search_by(|highlight| {
23215 Ordering::Equal
23216 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23217 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23218 });
23219 if let Err(ix) = ix {
23220 highlights.insert(ix, range);
23221 }
23222 self.gutter_highlights
23223 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23224 }
23225
23226 pub fn remove_gutter_highlights<T: 'static>(
23227 &mut self,
23228 ranges_to_remove: Vec<Range<Anchor>>,
23229 cx: &mut Context<Self>,
23230 ) {
23231 let snapshot = self.buffer().read(cx).snapshot(cx);
23232 let Some((color_fetcher, mut gutter_highlights)) =
23233 self.gutter_highlights.remove(&TypeId::of::<T>())
23234 else {
23235 return;
23236 };
23237 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23238 gutter_highlights.retain(|highlight| {
23239 while let Some(range_to_remove) = ranges_to_remove.peek() {
23240 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23241 Ordering::Less | Ordering::Equal => {
23242 ranges_to_remove.next();
23243 }
23244 Ordering::Greater => {
23245 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23246 Ordering::Less | Ordering::Equal => {
23247 return false;
23248 }
23249 Ordering::Greater => break,
23250 }
23251 }
23252 }
23253 }
23254
23255 true
23256 });
23257 self.gutter_highlights
23258 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23259 }
23260
23261 #[cfg(feature = "test-support")]
23262 pub fn all_text_highlights(
23263 &self,
23264 window: &mut Window,
23265 cx: &mut Context<Self>,
23266 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23267 let snapshot = self.snapshot(window, cx);
23268 self.display_map.update(cx, |display_map, _| {
23269 display_map
23270 .all_text_highlights()
23271 .map(|highlight| {
23272 let (style, ranges) = highlight.as_ref();
23273 (
23274 *style,
23275 ranges
23276 .iter()
23277 .map(|range| range.clone().to_display_points(&snapshot))
23278 .collect(),
23279 )
23280 })
23281 .collect()
23282 })
23283 }
23284
23285 #[cfg(feature = "test-support")]
23286 pub fn all_text_background_highlights(
23287 &self,
23288 window: &mut Window,
23289 cx: &mut Context<Self>,
23290 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23291 let snapshot = self.snapshot(window, cx);
23292 let buffer = &snapshot.buffer_snapshot();
23293 let start = buffer.anchor_before(MultiBufferOffset(0));
23294 let end = buffer.anchor_after(buffer.len());
23295 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23296 }
23297
23298 #[cfg(any(test, feature = "test-support"))]
23299 pub fn sorted_background_highlights_in_range(
23300 &self,
23301 search_range: Range<Anchor>,
23302 display_snapshot: &DisplaySnapshot,
23303 theme: &Theme,
23304 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23305 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23306 res.sort_by(|a, b| {
23307 a.0.start
23308 .cmp(&b.0.start)
23309 .then_with(|| a.0.end.cmp(&b.0.end))
23310 .then_with(|| a.1.cmp(&b.1))
23311 });
23312 res
23313 }
23314
23315 #[cfg(feature = "test-support")]
23316 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23317 let snapshot = self.buffer().read(cx).snapshot(cx);
23318
23319 let highlights = self
23320 .background_highlights
23321 .get(&HighlightKey::Type(TypeId::of::<
23322 items::BufferSearchHighlights,
23323 >()));
23324
23325 if let Some((_color, ranges)) = highlights {
23326 ranges
23327 .iter()
23328 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23329 .collect_vec()
23330 } else {
23331 vec![]
23332 }
23333 }
23334
23335 fn document_highlights_for_position<'a>(
23336 &'a self,
23337 position: Anchor,
23338 buffer: &'a MultiBufferSnapshot,
23339 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23340 let read_highlights = self
23341 .background_highlights
23342 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
23343 .map(|h| &h.1);
23344 let write_highlights = self
23345 .background_highlights
23346 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
23347 .map(|h| &h.1);
23348 let left_position = position.bias_left(buffer);
23349 let right_position = position.bias_right(buffer);
23350 read_highlights
23351 .into_iter()
23352 .chain(write_highlights)
23353 .flat_map(move |ranges| {
23354 let start_ix = match ranges.binary_search_by(|probe| {
23355 let cmp = probe.end.cmp(&left_position, buffer);
23356 if cmp.is_ge() {
23357 Ordering::Greater
23358 } else {
23359 Ordering::Less
23360 }
23361 }) {
23362 Ok(i) | Err(i) => i,
23363 };
23364
23365 ranges[start_ix..]
23366 .iter()
23367 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23368 })
23369 }
23370
23371 pub fn has_background_highlights<T: 'static>(&self) -> bool {
23372 self.background_highlights
23373 .get(&HighlightKey::Type(TypeId::of::<T>()))
23374 .is_some_and(|(_, highlights)| !highlights.is_empty())
23375 }
23376
23377 /// Returns all background highlights for a given range.
23378 ///
23379 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23380 pub fn background_highlights_in_range(
23381 &self,
23382 search_range: Range<Anchor>,
23383 display_snapshot: &DisplaySnapshot,
23384 theme: &Theme,
23385 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23386 let mut results = Vec::new();
23387 for (color_fetcher, ranges) in self.background_highlights.values() {
23388 let start_ix = match ranges.binary_search_by(|probe| {
23389 let cmp = probe
23390 .end
23391 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23392 if cmp.is_gt() {
23393 Ordering::Greater
23394 } else {
23395 Ordering::Less
23396 }
23397 }) {
23398 Ok(i) | Err(i) => i,
23399 };
23400 for (index, range) in ranges[start_ix..].iter().enumerate() {
23401 if range
23402 .start
23403 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23404 .is_ge()
23405 {
23406 break;
23407 }
23408
23409 let color = color_fetcher(&(start_ix + index), theme);
23410 let start = range.start.to_display_point(display_snapshot);
23411 let end = range.end.to_display_point(display_snapshot);
23412 results.push((start..end, color))
23413 }
23414 }
23415 results
23416 }
23417
23418 pub fn gutter_highlights_in_range(
23419 &self,
23420 search_range: Range<Anchor>,
23421 display_snapshot: &DisplaySnapshot,
23422 cx: &App,
23423 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23424 let mut results = Vec::new();
23425 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23426 let color = color_fetcher(cx);
23427 let start_ix = match ranges.binary_search_by(|probe| {
23428 let cmp = probe
23429 .end
23430 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23431 if cmp.is_gt() {
23432 Ordering::Greater
23433 } else {
23434 Ordering::Less
23435 }
23436 }) {
23437 Ok(i) | Err(i) => i,
23438 };
23439 for range in &ranges[start_ix..] {
23440 if range
23441 .start
23442 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23443 .is_ge()
23444 {
23445 break;
23446 }
23447
23448 let start = range.start.to_display_point(display_snapshot);
23449 let end = range.end.to_display_point(display_snapshot);
23450 results.push((start..end, color))
23451 }
23452 }
23453 results
23454 }
23455
23456 /// Get the text ranges corresponding to the redaction query
23457 pub fn redacted_ranges(
23458 &self,
23459 search_range: Range<Anchor>,
23460 display_snapshot: &DisplaySnapshot,
23461 cx: &App,
23462 ) -> Vec<Range<DisplayPoint>> {
23463 display_snapshot
23464 .buffer_snapshot()
23465 .redacted_ranges(search_range, |file| {
23466 if let Some(file) = file {
23467 file.is_private()
23468 && EditorSettings::get(
23469 Some(SettingsLocation {
23470 worktree_id: file.worktree_id(cx),
23471 path: file.path().as_ref(),
23472 }),
23473 cx,
23474 )
23475 .redact_private_values
23476 } else {
23477 false
23478 }
23479 })
23480 .map(|range| {
23481 range.start.to_display_point(display_snapshot)
23482 ..range.end.to_display_point(display_snapshot)
23483 })
23484 .collect()
23485 }
23486
23487 pub fn highlight_text_key<T: 'static>(
23488 &mut self,
23489 key: usize,
23490 ranges: Vec<Range<Anchor>>,
23491 style: HighlightStyle,
23492 merge: bool,
23493 cx: &mut Context<Self>,
23494 ) {
23495 self.display_map.update(cx, |map, cx| {
23496 map.highlight_text(
23497 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23498 ranges,
23499 style,
23500 merge,
23501 cx,
23502 );
23503 });
23504 cx.notify();
23505 }
23506
23507 pub fn highlight_text<T: 'static>(
23508 &mut self,
23509 ranges: Vec<Range<Anchor>>,
23510 style: HighlightStyle,
23511 cx: &mut Context<Self>,
23512 ) {
23513 self.display_map.update(cx, |map, cx| {
23514 map.highlight_text(
23515 HighlightKey::Type(TypeId::of::<T>()),
23516 ranges,
23517 style,
23518 false,
23519 cx,
23520 )
23521 });
23522 cx.notify();
23523 }
23524
23525 pub fn text_highlights<'a, T: 'static>(
23526 &'a self,
23527 cx: &'a App,
23528 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23529 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
23530 }
23531
23532 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
23533 let cleared = self
23534 .display_map
23535 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
23536 if cleared {
23537 cx.notify();
23538 }
23539 }
23540
23541 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23542 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23543 && self.focus_handle.is_focused(window)
23544 }
23545
23546 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23547 self.show_cursor_when_unfocused = is_enabled;
23548 cx.notify();
23549 }
23550
23551 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23552 cx.notify();
23553 }
23554
23555 fn on_debug_session_event(
23556 &mut self,
23557 _session: Entity<Session>,
23558 event: &SessionEvent,
23559 cx: &mut Context<Self>,
23560 ) {
23561 if let SessionEvent::InvalidateInlineValue = event {
23562 self.refresh_inline_values(cx);
23563 }
23564 }
23565
23566 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23567 let Some(project) = self.project.clone() else {
23568 return;
23569 };
23570
23571 if !self.inline_value_cache.enabled {
23572 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23573 self.splice_inlays(&inlays, Vec::new(), cx);
23574 return;
23575 }
23576
23577 let current_execution_position = self
23578 .highlighted_rows
23579 .get(&TypeId::of::<ActiveDebugLine>())
23580 .and_then(|lines| lines.last().map(|line| line.range.end));
23581
23582 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23583 let inline_values = editor
23584 .update(cx, |editor, cx| {
23585 let Some(current_execution_position) = current_execution_position else {
23586 return Some(Task::ready(Ok(Vec::new())));
23587 };
23588
23589 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23590 let snapshot = buffer.snapshot(cx);
23591
23592 let excerpt = snapshot.excerpt_containing(
23593 current_execution_position..current_execution_position,
23594 )?;
23595
23596 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23597 })?;
23598
23599 let range =
23600 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23601
23602 project.inline_values(buffer, range, cx)
23603 })
23604 .ok()
23605 .flatten()?
23606 .await
23607 .context("refreshing debugger inlays")
23608 .log_err()?;
23609
23610 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23611
23612 for (buffer_id, inline_value) in inline_values
23613 .into_iter()
23614 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23615 {
23616 buffer_inline_values
23617 .entry(buffer_id)
23618 .or_default()
23619 .push(inline_value);
23620 }
23621
23622 editor
23623 .update(cx, |editor, cx| {
23624 let snapshot = editor.buffer.read(cx).snapshot(cx);
23625 let mut new_inlays = Vec::default();
23626
23627 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23628 let buffer_id = buffer_snapshot.remote_id();
23629 buffer_inline_values
23630 .get(&buffer_id)
23631 .into_iter()
23632 .flatten()
23633 .for_each(|hint| {
23634 let inlay = Inlay::debugger(
23635 post_inc(&mut editor.next_inlay_id),
23636 Anchor::in_buffer(excerpt_id, hint.position),
23637 hint.text(),
23638 );
23639 if !inlay.text().chars().contains(&'\n') {
23640 new_inlays.push(inlay);
23641 }
23642 });
23643 }
23644
23645 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23646 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23647
23648 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23649 })
23650 .ok()?;
23651 Some(())
23652 });
23653 }
23654
23655 fn on_buffer_event(
23656 &mut self,
23657 multibuffer: &Entity<MultiBuffer>,
23658 event: &multi_buffer::Event,
23659 window: &mut Window,
23660 cx: &mut Context<Self>,
23661 ) {
23662 match event {
23663 multi_buffer::Event::Edited { edited_buffer } => {
23664 self.scrollbar_marker_state.dirty = true;
23665 self.active_indent_guides_state.dirty = true;
23666 self.refresh_active_diagnostics(cx);
23667 self.refresh_code_actions(window, cx);
23668 self.refresh_single_line_folds(window, cx);
23669 self.refresh_matching_bracket_highlights(window, cx);
23670 if self.has_active_edit_prediction() {
23671 self.update_visible_edit_prediction(window, cx);
23672 }
23673
23674 // Clean up orphaned review comments after edits
23675 self.cleanup_orphaned_review_comments(cx);
23676
23677 if let Some(buffer) = edited_buffer {
23678 if buffer.read(cx).file().is_none() {
23679 cx.emit(EditorEvent::TitleChanged);
23680 }
23681
23682 if self.project.is_some() {
23683 let buffer_id = buffer.read(cx).remote_id();
23684 self.register_buffer(buffer_id, cx);
23685 self.update_lsp_data(Some(buffer_id), window, cx);
23686 self.refresh_inlay_hints(
23687 InlayHintRefreshReason::BufferEdited(buffer_id),
23688 cx,
23689 );
23690 }
23691 }
23692
23693 cx.emit(EditorEvent::BufferEdited);
23694 cx.emit(SearchEvent::MatchesInvalidated);
23695
23696 let Some(project) = &self.project else { return };
23697 let (telemetry, is_via_ssh) = {
23698 let project = project.read(cx);
23699 let telemetry = project.client().telemetry().clone();
23700 let is_via_ssh = project.is_via_remote_server();
23701 (telemetry, is_via_ssh)
23702 };
23703 telemetry.log_edit_event("editor", is_via_ssh);
23704 }
23705 multi_buffer::Event::ExcerptsAdded {
23706 buffer,
23707 predecessor,
23708 excerpts,
23709 } => {
23710 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23711 let buffer_id = buffer.read(cx).remote_id();
23712 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23713 && let Some(project) = &self.project
23714 {
23715 update_uncommitted_diff_for_buffer(
23716 cx.entity(),
23717 project,
23718 [buffer.clone()],
23719 self.buffer.clone(),
23720 cx,
23721 )
23722 .detach();
23723 }
23724 self.update_lsp_data(Some(buffer_id), window, cx);
23725 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23726 self.colorize_brackets(false, cx);
23727 self.refresh_selected_text_highlights(true, window, cx);
23728 cx.emit(EditorEvent::ExcerptsAdded {
23729 buffer: buffer.clone(),
23730 predecessor: *predecessor,
23731 excerpts: excerpts.clone(),
23732 });
23733 }
23734 multi_buffer::Event::ExcerptsRemoved {
23735 ids,
23736 removed_buffer_ids,
23737 } => {
23738 if let Some(inlay_hints) = &mut self.inlay_hints {
23739 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23740 }
23741 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23742 for buffer_id in removed_buffer_ids {
23743 self.registered_buffers.remove(buffer_id);
23744 }
23745 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23746 cx.emit(EditorEvent::ExcerptsRemoved {
23747 ids: ids.clone(),
23748 removed_buffer_ids: removed_buffer_ids.clone(),
23749 });
23750 }
23751 multi_buffer::Event::ExcerptsEdited {
23752 excerpt_ids,
23753 buffer_ids,
23754 } => {
23755 self.display_map.update(cx, |map, cx| {
23756 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23757 });
23758 cx.emit(EditorEvent::ExcerptsEdited {
23759 ids: excerpt_ids.clone(),
23760 });
23761 }
23762 multi_buffer::Event::ExcerptsExpanded { ids } => {
23763 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23764 self.refresh_document_highlights(cx);
23765 for id in ids {
23766 self.fetched_tree_sitter_chunks.remove(id);
23767 }
23768 self.colorize_brackets(false, cx);
23769 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23770 }
23771 multi_buffer::Event::Reparsed(buffer_id) => {
23772 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23773 self.refresh_selected_text_highlights(true, window, cx);
23774 self.colorize_brackets(true, cx);
23775 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23776
23777 cx.emit(EditorEvent::Reparsed(*buffer_id));
23778 }
23779 multi_buffer::Event::DiffHunksToggled => {
23780 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23781 }
23782 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23783 if !is_fresh_language {
23784 self.registered_buffers.remove(&buffer_id);
23785 }
23786 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23787 cx.emit(EditorEvent::Reparsed(*buffer_id));
23788 cx.notify();
23789 }
23790 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
23791 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
23792 multi_buffer::Event::FileHandleChanged
23793 | multi_buffer::Event::Reloaded
23794 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
23795 multi_buffer::Event::DiagnosticsUpdated => {
23796 self.update_diagnostics_state(window, cx);
23797 }
23798 _ => {}
23799 };
23800 }
23801
23802 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
23803 if !self.diagnostics_enabled() {
23804 return;
23805 }
23806 self.refresh_active_diagnostics(cx);
23807 self.refresh_inline_diagnostics(true, window, cx);
23808 self.scrollbar_marker_state.dirty = true;
23809 cx.notify();
23810 }
23811
23812 pub fn start_temporary_diff_override(&mut self) {
23813 self.load_diff_task.take();
23814 self.temporary_diff_override = true;
23815 }
23816
23817 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
23818 self.temporary_diff_override = false;
23819 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
23820 self.buffer.update(cx, |buffer, cx| {
23821 buffer.set_all_diff_hunks_collapsed(cx);
23822 });
23823
23824 if let Some(project) = self.project.clone() {
23825 self.load_diff_task = Some(
23826 update_uncommitted_diff_for_buffer(
23827 cx.entity(),
23828 &project,
23829 self.buffer.read(cx).all_buffers(),
23830 self.buffer.clone(),
23831 cx,
23832 )
23833 .shared(),
23834 );
23835 }
23836 }
23837
23838 fn on_display_map_changed(
23839 &mut self,
23840 _: Entity<DisplayMap>,
23841 _: &mut Window,
23842 cx: &mut Context<Self>,
23843 ) {
23844 cx.notify();
23845 }
23846
23847 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
23848 if !self.mode.is_full() {
23849 return None;
23850 }
23851
23852 let theme_settings = theme::ThemeSettings::get_global(cx);
23853 let theme = cx.theme();
23854 let accent_colors = theme.accents().clone();
23855
23856 let accent_overrides = theme_settings
23857 .theme_overrides
23858 .get(theme.name.as_ref())
23859 .map(|theme_style| &theme_style.accents)
23860 .into_iter()
23861 .flatten()
23862 .chain(
23863 theme_settings
23864 .experimental_theme_overrides
23865 .as_ref()
23866 .map(|overrides| &overrides.accents)
23867 .into_iter()
23868 .flatten(),
23869 )
23870 .flat_map(|accent| accent.0.clone().map(SharedString::from))
23871 .collect();
23872
23873 Some(AccentData {
23874 colors: accent_colors,
23875 overrides: accent_overrides,
23876 })
23877 }
23878
23879 fn fetch_applicable_language_settings(
23880 &self,
23881 cx: &App,
23882 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
23883 if !self.mode.is_full() {
23884 return HashMap::default();
23885 }
23886
23887 self.buffer().read(cx).all_buffers().into_iter().fold(
23888 HashMap::default(),
23889 |mut acc, buffer| {
23890 let buffer = buffer.read(cx);
23891 let language = buffer.language().map(|language| language.name());
23892 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
23893 let file = buffer.file();
23894 v.insert(language_settings(language, file, cx).into_owned());
23895 }
23896 acc
23897 },
23898 )
23899 }
23900
23901 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23902 let new_language_settings = self.fetch_applicable_language_settings(cx);
23903 let language_settings_changed = new_language_settings != self.applicable_language_settings;
23904 self.applicable_language_settings = new_language_settings;
23905
23906 let new_accents = self.fetch_accent_data(cx);
23907 let accents_changed = new_accents != self.accent_data;
23908 self.accent_data = new_accents;
23909
23910 if self.diagnostics_enabled() {
23911 let new_severity = EditorSettings::get_global(cx)
23912 .diagnostics_max_severity
23913 .unwrap_or(DiagnosticSeverity::Hint);
23914 self.set_max_diagnostics_severity(new_severity, cx);
23915 }
23916 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23917 self.update_edit_prediction_settings(cx);
23918 self.refresh_edit_prediction(true, false, window, cx);
23919 self.refresh_inline_values(cx);
23920 self.refresh_inlay_hints(
23921 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
23922 self.selections.newest_anchor().head(),
23923 &self.buffer.read(cx).snapshot(cx),
23924 cx,
23925 )),
23926 cx,
23927 );
23928
23929 let old_cursor_shape = self.cursor_shape;
23930 let old_show_breadcrumbs = self.show_breadcrumbs;
23931
23932 {
23933 let editor_settings = EditorSettings::get_global(cx);
23934 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
23935 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
23936 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
23937 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
23938 }
23939
23940 if old_cursor_shape != self.cursor_shape {
23941 cx.emit(EditorEvent::CursorShapeChanged);
23942 }
23943
23944 if old_show_breadcrumbs != self.show_breadcrumbs {
23945 cx.emit(EditorEvent::BreadcrumbsChanged);
23946 }
23947
23948 let project_settings = ProjectSettings::get_global(cx);
23949 self.buffer_serialization = self
23950 .should_serialize_buffer()
23951 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
23952
23953 if self.mode.is_full() {
23954 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
23955 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
23956 if self.show_inline_diagnostics != show_inline_diagnostics {
23957 self.show_inline_diagnostics = show_inline_diagnostics;
23958 self.refresh_inline_diagnostics(false, window, cx);
23959 }
23960
23961 if self.git_blame_inline_enabled != inline_blame_enabled {
23962 self.toggle_git_blame_inline_internal(false, window, cx);
23963 }
23964
23965 let minimap_settings = EditorSettings::get_global(cx).minimap;
23966 if self.minimap_visibility != MinimapVisibility::Disabled {
23967 if self.minimap_visibility.settings_visibility()
23968 != minimap_settings.minimap_enabled()
23969 {
23970 self.set_minimap_visibility(
23971 MinimapVisibility::for_mode(self.mode(), cx),
23972 window,
23973 cx,
23974 );
23975 } else if let Some(minimap_entity) = self.minimap.as_ref() {
23976 minimap_entity.update(cx, |minimap_editor, cx| {
23977 minimap_editor.update_minimap_configuration(minimap_settings, cx)
23978 })
23979 }
23980 }
23981
23982 if language_settings_changed || accents_changed {
23983 self.colorize_brackets(true, cx);
23984 }
23985
23986 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
23987 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
23988 }) {
23989 if !inlay_splice.is_empty() {
23990 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
23991 }
23992 self.refresh_colors_for_visible_range(None, window, cx);
23993 }
23994 }
23995
23996 cx.notify();
23997 }
23998
23999 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24000 if !self.mode.is_full() {
24001 return;
24002 }
24003
24004 let new_accents = self.fetch_accent_data(cx);
24005 if new_accents != self.accent_data {
24006 self.accent_data = new_accents;
24007 self.colorize_brackets(true, cx);
24008 }
24009 }
24010
24011 pub fn set_searchable(&mut self, searchable: bool) {
24012 self.searchable = searchable;
24013 }
24014
24015 pub fn searchable(&self) -> bool {
24016 self.searchable
24017 }
24018
24019 pub fn open_excerpts_in_split(
24020 &mut self,
24021 _: &OpenExcerptsSplit,
24022 window: &mut Window,
24023 cx: &mut Context<Self>,
24024 ) {
24025 self.open_excerpts_common(None, true, window, cx)
24026 }
24027
24028 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24029 self.open_excerpts_common(None, false, window, cx)
24030 }
24031
24032 pub(crate) fn open_excerpts_common(
24033 &mut self,
24034 jump_data: Option<JumpData>,
24035 split: bool,
24036 window: &mut Window,
24037 cx: &mut Context<Self>,
24038 ) {
24039 let Some(workspace) = self.workspace() else {
24040 cx.propagate();
24041 return;
24042 };
24043
24044 if self.buffer.read(cx).is_singleton() {
24045 cx.propagate();
24046 return;
24047 }
24048
24049 let mut new_selections_by_buffer = HashMap::default();
24050 match &jump_data {
24051 Some(JumpData::MultiBufferPoint {
24052 excerpt_id,
24053 position,
24054 anchor,
24055 line_offset_from_top,
24056 }) => {
24057 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24058 if let Some(buffer) = multi_buffer_snapshot
24059 .buffer_id_for_excerpt(*excerpt_id)
24060 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24061 {
24062 let buffer_snapshot = buffer.read(cx).snapshot();
24063 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24064 language::ToPoint::to_point(anchor, &buffer_snapshot)
24065 } else {
24066 buffer_snapshot.clip_point(*position, Bias::Left)
24067 };
24068 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24069 new_selections_by_buffer.insert(
24070 buffer,
24071 (
24072 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24073 Some(*line_offset_from_top),
24074 ),
24075 );
24076 }
24077 }
24078 Some(JumpData::MultiBufferRow {
24079 row,
24080 line_offset_from_top,
24081 }) => {
24082 let point = MultiBufferPoint::new(row.0, 0);
24083 if let Some((buffer, buffer_point, _)) =
24084 self.buffer.read(cx).point_to_buffer_point(point, cx)
24085 {
24086 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24087 new_selections_by_buffer
24088 .entry(buffer)
24089 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24090 .0
24091 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24092 }
24093 }
24094 None => {
24095 let selections = self
24096 .selections
24097 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24098 let multi_buffer = self.buffer.read(cx);
24099 for selection in selections {
24100 for (snapshot, range, _, anchor) in multi_buffer
24101 .snapshot(cx)
24102 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24103 {
24104 if let Some(anchor) = anchor {
24105 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24106 else {
24107 continue;
24108 };
24109 let offset = text::ToOffset::to_offset(
24110 &anchor.text_anchor,
24111 &buffer_handle.read(cx).snapshot(),
24112 );
24113 let range = BufferOffset(offset)..BufferOffset(offset);
24114 new_selections_by_buffer
24115 .entry(buffer_handle)
24116 .or_insert((Vec::new(), None))
24117 .0
24118 .push(range)
24119 } else {
24120 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24121 else {
24122 continue;
24123 };
24124 new_selections_by_buffer
24125 .entry(buffer_handle)
24126 .or_insert((Vec::new(), None))
24127 .0
24128 .push(range)
24129 }
24130 }
24131 }
24132 }
24133 }
24134
24135 new_selections_by_buffer
24136 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24137
24138 if new_selections_by_buffer.is_empty() {
24139 return;
24140 }
24141
24142 // We defer the pane interaction because we ourselves are a workspace item
24143 // and activating a new item causes the pane to call a method on us reentrantly,
24144 // which panics if we're on the stack.
24145 window.defer(cx, move |window, cx| {
24146 workspace.update(cx, |workspace, cx| {
24147 let pane = if split {
24148 workspace.adjacent_pane(window, cx)
24149 } else {
24150 workspace.active_pane().clone()
24151 };
24152
24153 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24154 let buffer_read = buffer.read(cx);
24155 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24156 (true, project::File::from_dyn(Some(file)).is_some())
24157 } else {
24158 (false, false)
24159 };
24160
24161 // If project file is none workspace.open_project_item will fail to open the excerpt
24162 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24163 // so we check if there's a tab match in that case first
24164 let editor = (!has_file || !is_project_file)
24165 .then(|| {
24166 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24167 // so `workspace.open_project_item` will never find them, always opening a new editor.
24168 // Instead, we try to activate the existing editor in the pane first.
24169 let (editor, pane_item_index, pane_item_id) =
24170 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24171 let editor = item.downcast::<Editor>()?;
24172 let singleton_buffer =
24173 editor.read(cx).buffer().read(cx).as_singleton()?;
24174 if singleton_buffer == buffer {
24175 Some((editor, i, item.item_id()))
24176 } else {
24177 None
24178 }
24179 })?;
24180 pane.update(cx, |pane, cx| {
24181 pane.activate_item(pane_item_index, true, true, window, cx);
24182 if !PreviewTabsSettings::get_global(cx)
24183 .enable_preview_from_multibuffer
24184 {
24185 pane.unpreview_item_if_preview(pane_item_id);
24186 }
24187 });
24188 Some(editor)
24189 })
24190 .flatten()
24191 .unwrap_or_else(|| {
24192 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24193 .enable_keep_preview_on_code_navigation;
24194 let allow_new_preview =
24195 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
24196 workspace.open_project_item::<Self>(
24197 pane.clone(),
24198 buffer,
24199 true,
24200 true,
24201 keep_old_preview,
24202 allow_new_preview,
24203 window,
24204 cx,
24205 )
24206 });
24207
24208 editor.update(cx, |editor, cx| {
24209 if has_file && !is_project_file {
24210 editor.set_read_only(true);
24211 }
24212 let autoscroll = match scroll_offset {
24213 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
24214 None => Autoscroll::newest(),
24215 };
24216 let nav_history = editor.nav_history.take();
24217 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24218 let Some((&excerpt_id, _, buffer_snapshot)) =
24219 multibuffer_snapshot.as_singleton()
24220 else {
24221 return;
24222 };
24223 editor.change_selections(
24224 SelectionEffects::scroll(autoscroll),
24225 window,
24226 cx,
24227 |s| {
24228 s.select_ranges(ranges.into_iter().map(|range| {
24229 let range = buffer_snapshot.anchor_before(range.start)
24230 ..buffer_snapshot.anchor_after(range.end);
24231 multibuffer_snapshot
24232 .anchor_range_in_excerpt(excerpt_id, range)
24233 .unwrap()
24234 }));
24235 },
24236 );
24237 editor.nav_history = nav_history;
24238 });
24239 }
24240 })
24241 });
24242 }
24243
24244 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24245 let snapshot = self.buffer.read(cx).read(cx);
24246 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
24247 Some(
24248 ranges
24249 .iter()
24250 .map(move |range| {
24251 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24252 })
24253 .collect(),
24254 )
24255 }
24256
24257 fn selection_replacement_ranges(
24258 &self,
24259 range: Range<MultiBufferOffsetUtf16>,
24260 cx: &mut App,
24261 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24262 let selections = self
24263 .selections
24264 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24265 let newest_selection = selections
24266 .iter()
24267 .max_by_key(|selection| selection.id)
24268 .unwrap();
24269 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24270 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24271 let snapshot = self.buffer.read(cx).read(cx);
24272 selections
24273 .into_iter()
24274 .map(|mut selection| {
24275 selection.start.0.0 =
24276 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24277 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24278 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24279 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24280 })
24281 .collect()
24282 }
24283
24284 fn report_editor_event(
24285 &self,
24286 reported_event: ReportEditorEvent,
24287 file_extension: Option<String>,
24288 cx: &App,
24289 ) {
24290 if cfg!(any(test, feature = "test-support")) {
24291 return;
24292 }
24293
24294 let Some(project) = &self.project else { return };
24295
24296 // If None, we are in a file without an extension
24297 let file = self
24298 .buffer
24299 .read(cx)
24300 .as_singleton()
24301 .and_then(|b| b.read(cx).file());
24302 let file_extension = file_extension.or(file
24303 .as_ref()
24304 .and_then(|file| Path::new(file.file_name(cx)).extension())
24305 .and_then(|e| e.to_str())
24306 .map(|a| a.to_string()));
24307
24308 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24309 .map(|vim_mode| vim_mode.0)
24310 .unwrap_or(false);
24311
24312 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24313 let copilot_enabled = edit_predictions_provider
24314 == language::language_settings::EditPredictionProvider::Copilot;
24315 let copilot_enabled_for_language = self
24316 .buffer
24317 .read(cx)
24318 .language_settings(cx)
24319 .show_edit_predictions;
24320
24321 let project = project.read(cx);
24322 let event_type = reported_event.event_type();
24323
24324 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24325 telemetry::event!(
24326 event_type,
24327 type = if auto_saved {"autosave"} else {"manual"},
24328 file_extension,
24329 vim_mode,
24330 copilot_enabled,
24331 copilot_enabled_for_language,
24332 edit_predictions_provider,
24333 is_via_ssh = project.is_via_remote_server(),
24334 );
24335 } else {
24336 telemetry::event!(
24337 event_type,
24338 file_extension,
24339 vim_mode,
24340 copilot_enabled,
24341 copilot_enabled_for_language,
24342 edit_predictions_provider,
24343 is_via_ssh = project.is_via_remote_server(),
24344 );
24345 };
24346 }
24347
24348 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24349 /// with each line being an array of {text, highlight} objects.
24350 fn copy_highlight_json(
24351 &mut self,
24352 _: &CopyHighlightJson,
24353 window: &mut Window,
24354 cx: &mut Context<Self>,
24355 ) {
24356 #[derive(Serialize)]
24357 struct Chunk<'a> {
24358 text: String,
24359 highlight: Option<&'a str>,
24360 }
24361
24362 let snapshot = self.buffer.read(cx).snapshot(cx);
24363 let range = self
24364 .selected_text_range(false, window, cx)
24365 .and_then(|selection| {
24366 if selection.range.is_empty() {
24367 None
24368 } else {
24369 Some(
24370 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24371 selection.range.start,
24372 )))
24373 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24374 selection.range.end,
24375 ))),
24376 )
24377 }
24378 })
24379 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24380
24381 let chunks = snapshot.chunks(range, true);
24382 let mut lines = Vec::new();
24383 let mut line: VecDeque<Chunk> = VecDeque::new();
24384
24385 let Some(style) = self.style.as_ref() else {
24386 return;
24387 };
24388
24389 for chunk in chunks {
24390 let highlight = chunk
24391 .syntax_highlight_id
24392 .and_then(|id| id.name(&style.syntax));
24393 let mut chunk_lines = chunk.text.split('\n').peekable();
24394 while let Some(text) = chunk_lines.next() {
24395 let mut merged_with_last_token = false;
24396 if let Some(last_token) = line.back_mut()
24397 && last_token.highlight == highlight
24398 {
24399 last_token.text.push_str(text);
24400 merged_with_last_token = true;
24401 }
24402
24403 if !merged_with_last_token {
24404 line.push_back(Chunk {
24405 text: text.into(),
24406 highlight,
24407 });
24408 }
24409
24410 if chunk_lines.peek().is_some() {
24411 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24412 line.pop_front();
24413 }
24414 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24415 line.pop_back();
24416 }
24417
24418 lines.push(mem::take(&mut line));
24419 }
24420 }
24421 }
24422
24423 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24424 return;
24425 };
24426 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24427 }
24428
24429 pub fn open_context_menu(
24430 &mut self,
24431 _: &OpenContextMenu,
24432 window: &mut Window,
24433 cx: &mut Context<Self>,
24434 ) {
24435 self.request_autoscroll(Autoscroll::newest(), cx);
24436 let position = self
24437 .selections
24438 .newest_display(&self.display_snapshot(cx))
24439 .start;
24440 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24441 }
24442
24443 pub fn replay_insert_event(
24444 &mut self,
24445 text: &str,
24446 relative_utf16_range: Option<Range<isize>>,
24447 window: &mut Window,
24448 cx: &mut Context<Self>,
24449 ) {
24450 if !self.input_enabled {
24451 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24452 return;
24453 }
24454 if let Some(relative_utf16_range) = relative_utf16_range {
24455 let selections = self
24456 .selections
24457 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24458 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24459 let new_ranges = selections.into_iter().map(|range| {
24460 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24461 range
24462 .head()
24463 .0
24464 .0
24465 .saturating_add_signed(relative_utf16_range.start),
24466 ));
24467 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24468 range
24469 .head()
24470 .0
24471 .0
24472 .saturating_add_signed(relative_utf16_range.end),
24473 ));
24474 start..end
24475 });
24476 s.select_ranges(new_ranges);
24477 });
24478 }
24479
24480 self.handle_input(text, window, cx);
24481 }
24482
24483 pub fn is_focused(&self, window: &Window) -> bool {
24484 self.focus_handle.is_focused(window)
24485 }
24486
24487 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24488 cx.emit(EditorEvent::Focused);
24489
24490 if let Some(descendant) = self
24491 .last_focused_descendant
24492 .take()
24493 .and_then(|descendant| descendant.upgrade())
24494 {
24495 window.focus(&descendant, cx);
24496 } else {
24497 if let Some(blame) = self.blame.as_ref() {
24498 blame.update(cx, GitBlame::focus)
24499 }
24500
24501 self.blink_manager.update(cx, BlinkManager::enable);
24502 self.show_cursor_names(window, cx);
24503 self.buffer.update(cx, |buffer, cx| {
24504 buffer.finalize_last_transaction(cx);
24505 if self.leader_id.is_none() {
24506 buffer.set_active_selections(
24507 &self.selections.disjoint_anchors_arc(),
24508 self.selections.line_mode(),
24509 self.cursor_shape,
24510 cx,
24511 );
24512 }
24513 });
24514
24515 if let Some(position_map) = self.last_position_map.clone() {
24516 EditorElement::mouse_moved(
24517 self,
24518 &MouseMoveEvent {
24519 position: window.mouse_position(),
24520 pressed_button: None,
24521 modifiers: window.modifiers(),
24522 },
24523 &position_map,
24524 None,
24525 window,
24526 cx,
24527 );
24528 }
24529 }
24530 }
24531
24532 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24533 cx.emit(EditorEvent::FocusedIn)
24534 }
24535
24536 fn handle_focus_out(
24537 &mut self,
24538 event: FocusOutEvent,
24539 _window: &mut Window,
24540 cx: &mut Context<Self>,
24541 ) {
24542 if event.blurred != self.focus_handle {
24543 self.last_focused_descendant = Some(event.blurred);
24544 }
24545 self.selection_drag_state = SelectionDragState::None;
24546 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24547 }
24548
24549 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24550 self.blink_manager.update(cx, BlinkManager::disable);
24551 self.buffer
24552 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24553
24554 if let Some(blame) = self.blame.as_ref() {
24555 blame.update(cx, GitBlame::blur)
24556 }
24557 if !self.hover_state.focused(window, cx) {
24558 hide_hover(self, cx);
24559 }
24560 if !self
24561 .context_menu
24562 .borrow()
24563 .as_ref()
24564 .is_some_and(|context_menu| context_menu.focused(window, cx))
24565 {
24566 self.hide_context_menu(window, cx);
24567 }
24568 self.take_active_edit_prediction(cx);
24569 cx.emit(EditorEvent::Blurred);
24570 cx.notify();
24571 }
24572
24573 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24574 let mut pending: String = window
24575 .pending_input_keystrokes()
24576 .into_iter()
24577 .flatten()
24578 .filter_map(|keystroke| keystroke.key_char.clone())
24579 .collect();
24580
24581 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24582 pending = "".to_string();
24583 }
24584
24585 let existing_pending = self
24586 .text_highlights::<PendingInput>(cx)
24587 .map(|(_, ranges)| ranges.to_vec());
24588 if existing_pending.is_none() && pending.is_empty() {
24589 return;
24590 }
24591 let transaction =
24592 self.transact(window, cx, |this, window, cx| {
24593 let selections = this
24594 .selections
24595 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24596 let edits = selections
24597 .iter()
24598 .map(|selection| (selection.end..selection.end, pending.clone()));
24599 this.edit(edits, cx);
24600 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24601 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24602 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24603 }));
24604 });
24605 if let Some(existing_ranges) = existing_pending {
24606 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24607 this.edit(edits, cx);
24608 }
24609 });
24610
24611 let snapshot = self.snapshot(window, cx);
24612 let ranges = self
24613 .selections
24614 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24615 .into_iter()
24616 .map(|selection| {
24617 snapshot.buffer_snapshot().anchor_after(selection.end)
24618 ..snapshot
24619 .buffer_snapshot()
24620 .anchor_before(selection.end + pending.len())
24621 })
24622 .collect();
24623
24624 if pending.is_empty() {
24625 self.clear_highlights::<PendingInput>(cx);
24626 } else {
24627 self.highlight_text::<PendingInput>(
24628 ranges,
24629 HighlightStyle {
24630 underline: Some(UnderlineStyle {
24631 thickness: px(1.),
24632 color: None,
24633 wavy: false,
24634 }),
24635 ..Default::default()
24636 },
24637 cx,
24638 );
24639 }
24640
24641 self.ime_transaction = self.ime_transaction.or(transaction);
24642 if let Some(transaction) = self.ime_transaction {
24643 self.buffer.update(cx, |buffer, cx| {
24644 buffer.group_until_transaction(transaction, cx);
24645 });
24646 }
24647
24648 if self.text_highlights::<PendingInput>(cx).is_none() {
24649 self.ime_transaction.take();
24650 }
24651 }
24652
24653 pub fn register_action_renderer(
24654 &mut self,
24655 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24656 ) -> Subscription {
24657 let id = self.next_editor_action_id.post_inc();
24658 self.editor_actions
24659 .borrow_mut()
24660 .insert(id, Box::new(listener));
24661
24662 let editor_actions = self.editor_actions.clone();
24663 Subscription::new(move || {
24664 editor_actions.borrow_mut().remove(&id);
24665 })
24666 }
24667
24668 pub fn register_action<A: Action>(
24669 &mut self,
24670 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24671 ) -> Subscription {
24672 let id = self.next_editor_action_id.post_inc();
24673 let listener = Arc::new(listener);
24674 self.editor_actions.borrow_mut().insert(
24675 id,
24676 Box::new(move |_, window, _| {
24677 let listener = listener.clone();
24678 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24679 let action = action.downcast_ref().unwrap();
24680 if phase == DispatchPhase::Bubble {
24681 listener(action, window, cx)
24682 }
24683 })
24684 }),
24685 );
24686
24687 let editor_actions = self.editor_actions.clone();
24688 Subscription::new(move || {
24689 editor_actions.borrow_mut().remove(&id);
24690 })
24691 }
24692
24693 pub fn file_header_size(&self) -> u32 {
24694 FILE_HEADER_HEIGHT
24695 }
24696
24697 pub fn restore(
24698 &mut self,
24699 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24700 window: &mut Window,
24701 cx: &mut Context<Self>,
24702 ) {
24703 self.buffer().update(cx, |multi_buffer, cx| {
24704 for (buffer_id, changes) in revert_changes {
24705 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24706 buffer.update(cx, |buffer, cx| {
24707 buffer.edit(
24708 changes
24709 .into_iter()
24710 .map(|(range, text)| (range, text.to_string())),
24711 None,
24712 cx,
24713 );
24714 });
24715 }
24716 }
24717 });
24718 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24719 selections.refresh()
24720 });
24721 }
24722
24723 pub fn to_pixel_point(
24724 &mut self,
24725 source: multi_buffer::Anchor,
24726 editor_snapshot: &EditorSnapshot,
24727 window: &mut Window,
24728 cx: &App,
24729 ) -> Option<gpui::Point<Pixels>> {
24730 let source_point = source.to_display_point(editor_snapshot);
24731 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
24732 }
24733
24734 pub fn display_to_pixel_point(
24735 &mut self,
24736 source: DisplayPoint,
24737 editor_snapshot: &EditorSnapshot,
24738 window: &mut Window,
24739 cx: &App,
24740 ) -> Option<gpui::Point<Pixels>> {
24741 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
24742 let text_layout_details = self.text_layout_details(window);
24743 let scroll_top = text_layout_details
24744 .scroll_anchor
24745 .scroll_position(editor_snapshot)
24746 .y;
24747
24748 if source.row().as_f64() < scroll_top.floor() {
24749 return None;
24750 }
24751 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
24752 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
24753 Some(gpui::Point::new(source_x, source_y))
24754 }
24755
24756 pub fn has_visible_completions_menu(&self) -> bool {
24757 !self.edit_prediction_preview_is_active()
24758 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
24759 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
24760 })
24761 }
24762
24763 pub fn register_addon<T: Addon>(&mut self, instance: T) {
24764 if self.mode.is_minimap() {
24765 return;
24766 }
24767 self.addons
24768 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
24769 }
24770
24771 pub fn unregister_addon<T: Addon>(&mut self) {
24772 self.addons.remove(&std::any::TypeId::of::<T>());
24773 }
24774
24775 pub fn addon<T: Addon>(&self) -> Option<&T> {
24776 let type_id = std::any::TypeId::of::<T>();
24777 self.addons
24778 .get(&type_id)
24779 .and_then(|item| item.to_any().downcast_ref::<T>())
24780 }
24781
24782 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
24783 let type_id = std::any::TypeId::of::<T>();
24784 self.addons
24785 .get_mut(&type_id)
24786 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
24787 }
24788
24789 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
24790 let text_layout_details = self.text_layout_details(window);
24791 let style = &text_layout_details.editor_style;
24792 let font_id = window.text_system().resolve_font(&style.text.font());
24793 let font_size = style.text.font_size.to_pixels(window.rem_size());
24794 let line_height = style.text.line_height_in_pixels(window.rem_size());
24795 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
24796 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
24797
24798 CharacterDimensions {
24799 em_width,
24800 em_advance,
24801 line_height,
24802 }
24803 }
24804
24805 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
24806 self.load_diff_task.clone()
24807 }
24808
24809 fn read_metadata_from_db(
24810 &mut self,
24811 item_id: u64,
24812 workspace_id: WorkspaceId,
24813 window: &mut Window,
24814 cx: &mut Context<Editor>,
24815 ) {
24816 if self.buffer_kind(cx) == ItemBufferKind::Singleton
24817 && !self.mode.is_minimap()
24818 && WorkspaceSettings::get(None, cx).restore_on_startup
24819 != RestoreOnStartupBehavior::EmptyTab
24820 {
24821 let buffer_snapshot = OnceCell::new();
24822
24823 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
24824 && !folds.is_empty()
24825 {
24826 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24827 let snapshot_len = snapshot.len().0;
24828
24829 // Helper: search for fingerprint in buffer, return offset if found
24830 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
24831 // Ensure we start at a character boundary (defensive)
24832 let search_start = snapshot
24833 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
24834 .0;
24835 let search_end = snapshot_len.saturating_sub(fingerprint.len());
24836
24837 let mut byte_offset = search_start;
24838 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
24839 if byte_offset > search_end {
24840 break;
24841 }
24842 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
24843 return Some(byte_offset);
24844 }
24845 byte_offset += ch.len_utf8();
24846 }
24847 None
24848 };
24849
24850 // Track search position to handle duplicate fingerprints correctly.
24851 // Folds are stored in document order, so we advance after each match.
24852 let mut search_start = 0usize;
24853
24854 let valid_folds: Vec<_> = folds
24855 .into_iter()
24856 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
24857 // Skip folds without fingerprints (old data before migration)
24858 let sfp = start_fp?;
24859 let efp = end_fp?;
24860 let efp_len = efp.len();
24861
24862 // Fast path: check if fingerprints match at stored offsets
24863 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
24864 let start_matches = stored_start < snapshot_len
24865 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
24866 let efp_check_pos = stored_end.saturating_sub(efp_len);
24867 let end_matches = efp_check_pos >= stored_start
24868 && stored_end <= snapshot_len
24869 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
24870
24871 let (new_start, new_end) = if start_matches && end_matches {
24872 // Offsets unchanged, use stored values
24873 (stored_start, stored_end)
24874 } else if sfp == efp {
24875 // Short fold: identical fingerprints can only match once per search
24876 // Use stored fold length to compute new_end
24877 let new_start = find_fingerprint(&sfp, search_start)?;
24878 let fold_len = stored_end - stored_start;
24879 let new_end = new_start + fold_len;
24880 (new_start, new_end)
24881 } else {
24882 // Slow path: search for fingerprints in buffer
24883 let new_start = find_fingerprint(&sfp, search_start)?;
24884 // Search for end_fp after start, then add efp_len to get actual fold end
24885 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
24886 let new_end = efp_pos + efp_len;
24887 (new_start, new_end)
24888 };
24889
24890 // Advance search position for next fold
24891 search_start = new_end;
24892
24893 // Validate fold makes sense (end must be after start)
24894 if new_end <= new_start {
24895 return None;
24896 }
24897
24898 Some(
24899 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
24900 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
24901 )
24902 })
24903 .collect();
24904
24905 if !valid_folds.is_empty() {
24906 self.fold_ranges(valid_folds, false, window, cx);
24907
24908 // Migrate folds to current entity_id before workspace cleanup runs.
24909 // Entity IDs change between sessions, but workspace cleanup deletes
24910 // old editor rows (cascading to folds) based on current entity IDs.
24911 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
24912 if new_editor_id != item_id {
24913 cx.spawn(async move |_, _| {
24914 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
24915 .await
24916 .log_err();
24917 })
24918 .detach();
24919 }
24920 }
24921 }
24922
24923 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
24924 && !selections.is_empty()
24925 {
24926 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24927 // skip adding the initial selection to selection history
24928 self.selection_history.mode = SelectionHistoryMode::Skipping;
24929 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24930 s.select_ranges(selections.into_iter().map(|(start, end)| {
24931 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
24932 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
24933 }));
24934 });
24935 self.selection_history.mode = SelectionHistoryMode::Normal;
24936 };
24937 }
24938
24939 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
24940 }
24941
24942 fn update_lsp_data(
24943 &mut self,
24944 for_buffer: Option<BufferId>,
24945 window: &mut Window,
24946 cx: &mut Context<'_, Self>,
24947 ) {
24948 if let Some(buffer_id) = for_buffer {
24949 self.pull_diagnostics(buffer_id, window, cx);
24950 }
24951 self.refresh_colors_for_visible_range(for_buffer, window, cx);
24952 }
24953
24954 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
24955 if self.ignore_lsp_data() {
24956 return;
24957 }
24958 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
24959 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
24960 }
24961 }
24962
24963 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
24964 if self.ignore_lsp_data() {
24965 return;
24966 }
24967
24968 if !self.registered_buffers.contains_key(&buffer_id)
24969 && let Some(project) = self.project.as_ref()
24970 {
24971 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
24972 project.update(cx, |project, cx| {
24973 self.registered_buffers.insert(
24974 buffer_id,
24975 project.register_buffer_with_language_servers(&buffer, cx),
24976 );
24977 });
24978 } else {
24979 self.registered_buffers.remove(&buffer_id);
24980 }
24981 }
24982 }
24983
24984 fn ignore_lsp_data(&self) -> bool {
24985 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
24986 // skip any LSP updates for it.
24987 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
24988 }
24989
24990 pub(crate) fn create_style(&self, cx: &App) -> EditorStyle {
24991 let settings = ThemeSettings::get_global(cx);
24992
24993 let mut text_style = match self.mode {
24994 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24995 color: cx.theme().colors().editor_foreground,
24996 font_family: settings.ui_font.family.clone(),
24997 font_features: settings.ui_font.features.clone(),
24998 font_fallbacks: settings.ui_font.fallbacks.clone(),
24999 font_size: rems(0.875).into(),
25000 font_weight: settings.ui_font.weight,
25001 line_height: relative(settings.buffer_line_height.value()),
25002 ..Default::default()
25003 },
25004 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25005 color: cx.theme().colors().editor_foreground,
25006 font_family: settings.buffer_font.family.clone(),
25007 font_features: settings.buffer_font.features.clone(),
25008 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25009 font_size: settings.buffer_font_size(cx).into(),
25010 font_weight: settings.buffer_font.weight,
25011 line_height: relative(settings.buffer_line_height.value()),
25012 ..Default::default()
25013 },
25014 };
25015 if let Some(text_style_refinement) = &self.text_style_refinement {
25016 text_style.refine(text_style_refinement)
25017 }
25018
25019 let background = match self.mode {
25020 EditorMode::SingleLine => cx.theme().system().transparent,
25021 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25022 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25023 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25024 };
25025
25026 EditorStyle {
25027 background,
25028 border: cx.theme().colors().border,
25029 local_player: cx.theme().players().local(),
25030 text: text_style,
25031 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25032 syntax: cx.theme().syntax().clone(),
25033 status: cx.theme().status().clone(),
25034 inlay_hints_style: make_inlay_hints_style(cx),
25035 edit_prediction_styles: make_suggestion_styles(cx),
25036 unnecessary_code_fade: settings.unnecessary_code_fade,
25037 show_underlines: self.diagnostics_enabled(),
25038 }
25039 }
25040 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
25041 let cursor = self.selections.newest_anchor().head();
25042 let multibuffer = self.buffer().read(cx);
25043 let is_singleton = multibuffer.is_singleton();
25044 let (buffer_id, symbols) = multibuffer
25045 .read(cx)
25046 .symbols_containing(cursor, Some(variant.syntax()))?;
25047 let buffer = multibuffer.buffer(buffer_id)?;
25048
25049 let buffer = buffer.read(cx);
25050 let settings = ThemeSettings::get_global(cx);
25051 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25052 let mut breadcrumbs = if is_singleton {
25053 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25054 buffer
25055 .snapshot()
25056 .resolve_file_path(
25057 self.project
25058 .as_ref()
25059 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25060 .unwrap_or_default(),
25061 cx,
25062 )
25063 .unwrap_or_else(|| {
25064 if multibuffer.is_singleton() {
25065 multibuffer.title(cx).to_string()
25066 } else {
25067 "untitled".to_string()
25068 }
25069 })
25070 });
25071 vec![BreadcrumbText {
25072 text,
25073 highlights: None,
25074 font: Some(settings.buffer_font.clone()),
25075 }]
25076 } else {
25077 vec![]
25078 };
25079
25080 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
25081 text: symbol.text,
25082 highlights: Some(symbol.highlight_ranges),
25083 font: Some(settings.buffer_font.clone()),
25084 }));
25085 Some(breadcrumbs)
25086 }
25087}
25088
25089fn edit_for_markdown_paste<'a>(
25090 buffer: &MultiBufferSnapshot,
25091 range: Range<MultiBufferOffset>,
25092 to_insert: &'a str,
25093 url: Option<url::Url>,
25094) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25095 if url.is_none() {
25096 return (range, Cow::Borrowed(to_insert));
25097 };
25098
25099 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25100
25101 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25102 Cow::Borrowed(to_insert)
25103 } else {
25104 Cow::Owned(format!("[{old_text}]({to_insert})"))
25105 };
25106 (range, new_text)
25107}
25108
25109fn process_completion_for_edit(
25110 completion: &Completion,
25111 intent: CompletionIntent,
25112 buffer: &Entity<Buffer>,
25113 cursor_position: &text::Anchor,
25114 cx: &mut Context<Editor>,
25115) -> CompletionEdit {
25116 let buffer = buffer.read(cx);
25117 let buffer_snapshot = buffer.snapshot();
25118 let (snippet, new_text) = if completion.is_snippet() {
25119 let mut snippet_source = completion.new_text.clone();
25120 // Workaround for typescript language server issues so that methods don't expand within
25121 // strings and functions with type expressions. The previous point is used because the query
25122 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25123 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25124 let previous_point = if previous_point.column > 0 {
25125 cursor_position.to_previous_offset(&buffer_snapshot)
25126 } else {
25127 cursor_position.to_offset(&buffer_snapshot)
25128 };
25129 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25130 && scope.prefers_label_for_snippet_in_completion()
25131 && let Some(label) = completion.label()
25132 && matches!(
25133 completion.kind(),
25134 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25135 )
25136 {
25137 snippet_source = label;
25138 }
25139 match Snippet::parse(&snippet_source).log_err() {
25140 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25141 None => (None, completion.new_text.clone()),
25142 }
25143 } else {
25144 (None, completion.new_text.clone())
25145 };
25146
25147 let mut range_to_replace = {
25148 let replace_range = &completion.replace_range;
25149 if let CompletionSource::Lsp {
25150 insert_range: Some(insert_range),
25151 ..
25152 } = &completion.source
25153 {
25154 debug_assert_eq!(
25155 insert_range.start, replace_range.start,
25156 "insert_range and replace_range should start at the same position"
25157 );
25158 debug_assert!(
25159 insert_range
25160 .start
25161 .cmp(cursor_position, &buffer_snapshot)
25162 .is_le(),
25163 "insert_range should start before or at cursor position"
25164 );
25165 debug_assert!(
25166 replace_range
25167 .start
25168 .cmp(cursor_position, &buffer_snapshot)
25169 .is_le(),
25170 "replace_range should start before or at cursor position"
25171 );
25172
25173 let should_replace = match intent {
25174 CompletionIntent::CompleteWithInsert => false,
25175 CompletionIntent::CompleteWithReplace => true,
25176 CompletionIntent::Complete | CompletionIntent::Compose => {
25177 let insert_mode =
25178 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25179 .completions
25180 .lsp_insert_mode;
25181 match insert_mode {
25182 LspInsertMode::Insert => false,
25183 LspInsertMode::Replace => true,
25184 LspInsertMode::ReplaceSubsequence => {
25185 let mut text_to_replace = buffer.chars_for_range(
25186 buffer.anchor_before(replace_range.start)
25187 ..buffer.anchor_after(replace_range.end),
25188 );
25189 let mut current_needle = text_to_replace.next();
25190 for haystack_ch in completion.label.text.chars() {
25191 if let Some(needle_ch) = current_needle
25192 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25193 {
25194 current_needle = text_to_replace.next();
25195 }
25196 }
25197 current_needle.is_none()
25198 }
25199 LspInsertMode::ReplaceSuffix => {
25200 if replace_range
25201 .end
25202 .cmp(cursor_position, &buffer_snapshot)
25203 .is_gt()
25204 {
25205 let range_after_cursor = *cursor_position..replace_range.end;
25206 let text_after_cursor = buffer
25207 .text_for_range(
25208 buffer.anchor_before(range_after_cursor.start)
25209 ..buffer.anchor_after(range_after_cursor.end),
25210 )
25211 .collect::<String>()
25212 .to_ascii_lowercase();
25213 completion
25214 .label
25215 .text
25216 .to_ascii_lowercase()
25217 .ends_with(&text_after_cursor)
25218 } else {
25219 true
25220 }
25221 }
25222 }
25223 }
25224 };
25225
25226 if should_replace {
25227 replace_range.clone()
25228 } else {
25229 insert_range.clone()
25230 }
25231 } else {
25232 replace_range.clone()
25233 }
25234 };
25235
25236 if range_to_replace
25237 .end
25238 .cmp(cursor_position, &buffer_snapshot)
25239 .is_lt()
25240 {
25241 range_to_replace.end = *cursor_position;
25242 }
25243
25244 let replace_range = range_to_replace.to_offset(buffer);
25245 CompletionEdit {
25246 new_text,
25247 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25248 snippet,
25249 }
25250}
25251
25252struct CompletionEdit {
25253 new_text: String,
25254 replace_range: Range<BufferOffset>,
25255 snippet: Option<Snippet>,
25256}
25257
25258fn comment_delimiter_for_newline(
25259 start_point: &Point,
25260 buffer: &MultiBufferSnapshot,
25261 language: &LanguageScope,
25262) -> Option<Arc<str>> {
25263 let delimiters = language.line_comment_prefixes();
25264 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25265 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25266
25267 let num_of_whitespaces = snapshot
25268 .chars_for_range(range.clone())
25269 .take_while(|c| c.is_whitespace())
25270 .count();
25271 let comment_candidate = snapshot
25272 .chars_for_range(range.clone())
25273 .skip(num_of_whitespaces)
25274 .take(max_len_of_delimiter)
25275 .collect::<String>();
25276 let (delimiter, trimmed_len) = delimiters
25277 .iter()
25278 .filter_map(|delimiter| {
25279 let prefix = delimiter.trim_end();
25280 if comment_candidate.starts_with(prefix) {
25281 Some((delimiter, prefix.len()))
25282 } else {
25283 None
25284 }
25285 })
25286 .max_by_key(|(_, len)| *len)?;
25287
25288 if let Some(BlockCommentConfig {
25289 start: block_start, ..
25290 }) = language.block_comment()
25291 {
25292 let block_start_trimmed = block_start.trim_end();
25293 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25294 let line_content = snapshot
25295 .chars_for_range(range)
25296 .skip(num_of_whitespaces)
25297 .take(block_start_trimmed.len())
25298 .collect::<String>();
25299
25300 if line_content.starts_with(block_start_trimmed) {
25301 return None;
25302 }
25303 }
25304 }
25305
25306 let cursor_is_placed_after_comment_marker =
25307 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25308 if cursor_is_placed_after_comment_marker {
25309 Some(delimiter.clone())
25310 } else {
25311 None
25312 }
25313}
25314
25315fn documentation_delimiter_for_newline(
25316 start_point: &Point,
25317 buffer: &MultiBufferSnapshot,
25318 language: &LanguageScope,
25319 newline_config: &mut NewlineConfig,
25320) -> Option<Arc<str>> {
25321 let BlockCommentConfig {
25322 start: start_tag,
25323 end: end_tag,
25324 prefix: delimiter,
25325 tab_size: len,
25326 } = language.documentation_comment()?;
25327 let is_within_block_comment = buffer
25328 .language_scope_at(*start_point)
25329 .is_some_and(|scope| scope.override_name() == Some("comment"));
25330 if !is_within_block_comment {
25331 return None;
25332 }
25333
25334 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25335
25336 let num_of_whitespaces = snapshot
25337 .chars_for_range(range.clone())
25338 .take_while(|c| c.is_whitespace())
25339 .count();
25340
25341 // 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.
25342 let column = start_point.column;
25343 let cursor_is_after_start_tag = {
25344 let start_tag_len = start_tag.len();
25345 let start_tag_line = snapshot
25346 .chars_for_range(range.clone())
25347 .skip(num_of_whitespaces)
25348 .take(start_tag_len)
25349 .collect::<String>();
25350 if start_tag_line.starts_with(start_tag.as_ref()) {
25351 num_of_whitespaces + start_tag_len <= column as usize
25352 } else {
25353 false
25354 }
25355 };
25356
25357 let cursor_is_after_delimiter = {
25358 let delimiter_trim = delimiter.trim_end();
25359 let delimiter_line = snapshot
25360 .chars_for_range(range.clone())
25361 .skip(num_of_whitespaces)
25362 .take(delimiter_trim.len())
25363 .collect::<String>();
25364 if delimiter_line.starts_with(delimiter_trim) {
25365 num_of_whitespaces + delimiter_trim.len() <= column as usize
25366 } else {
25367 false
25368 }
25369 };
25370
25371 let mut needs_extra_line = false;
25372 let mut extra_line_additional_indent = IndentSize::spaces(0);
25373
25374 let cursor_is_before_end_tag_if_exists = {
25375 let mut char_position = 0u32;
25376 let mut end_tag_offset = None;
25377
25378 'outer: for chunk in snapshot.text_for_range(range) {
25379 if let Some(byte_pos) = chunk.find(&**end_tag) {
25380 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25381 end_tag_offset = Some(char_position + chars_before_match);
25382 break 'outer;
25383 }
25384 char_position += chunk.chars().count() as u32;
25385 }
25386
25387 if let Some(end_tag_offset) = end_tag_offset {
25388 let cursor_is_before_end_tag = column <= end_tag_offset;
25389 if cursor_is_after_start_tag {
25390 if cursor_is_before_end_tag {
25391 needs_extra_line = true;
25392 }
25393 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25394 if cursor_is_at_start_of_end_tag {
25395 extra_line_additional_indent.len = *len;
25396 }
25397 }
25398 cursor_is_before_end_tag
25399 } else {
25400 true
25401 }
25402 };
25403
25404 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25405 && cursor_is_before_end_tag_if_exists
25406 {
25407 let additional_indent = if cursor_is_after_start_tag {
25408 IndentSize::spaces(*len)
25409 } else {
25410 IndentSize::spaces(0)
25411 };
25412
25413 *newline_config = NewlineConfig::Newline {
25414 additional_indent,
25415 extra_line_additional_indent: if needs_extra_line {
25416 Some(extra_line_additional_indent)
25417 } else {
25418 None
25419 },
25420 prevent_auto_indent: true,
25421 };
25422 Some(delimiter.clone())
25423 } else {
25424 None
25425 }
25426}
25427
25428const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25429
25430fn list_delimiter_for_newline(
25431 start_point: &Point,
25432 buffer: &MultiBufferSnapshot,
25433 language: &LanguageScope,
25434 newline_config: &mut NewlineConfig,
25435) -> Option<Arc<str>> {
25436 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25437
25438 let num_of_whitespaces = snapshot
25439 .chars_for_range(range.clone())
25440 .take_while(|c| c.is_whitespace())
25441 .count();
25442
25443 let task_list_entries: Vec<_> = language
25444 .task_list()
25445 .into_iter()
25446 .flat_map(|config| {
25447 config
25448 .prefixes
25449 .iter()
25450 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25451 })
25452 .collect();
25453 let unordered_list_entries: Vec<_> = language
25454 .unordered_list()
25455 .iter()
25456 .map(|marker| (marker.as_ref(), marker.as_ref()))
25457 .collect();
25458
25459 let all_entries: Vec<_> = task_list_entries
25460 .into_iter()
25461 .chain(unordered_list_entries)
25462 .collect();
25463
25464 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25465 let candidate: String = snapshot
25466 .chars_for_range(range.clone())
25467 .skip(num_of_whitespaces)
25468 .take(max_prefix_len)
25469 .collect();
25470
25471 if let Some((prefix, continuation)) = all_entries
25472 .iter()
25473 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25474 .max_by_key(|(prefix, _)| prefix.len())
25475 {
25476 let end_of_prefix = num_of_whitespaces + prefix.len();
25477 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25478 let has_content_after_marker = snapshot
25479 .chars_for_range(range)
25480 .skip(end_of_prefix)
25481 .any(|c| !c.is_whitespace());
25482
25483 if has_content_after_marker && cursor_is_after_prefix {
25484 return Some((*continuation).into());
25485 }
25486
25487 if start_point.column as usize == end_of_prefix {
25488 if num_of_whitespaces == 0 {
25489 *newline_config = NewlineConfig::ClearCurrentLine;
25490 } else {
25491 *newline_config = NewlineConfig::UnindentCurrentLine {
25492 continuation: (*continuation).into(),
25493 };
25494 }
25495 }
25496
25497 return None;
25498 }
25499 }
25500
25501 let candidate: String = snapshot
25502 .chars_for_range(range.clone())
25503 .skip(num_of_whitespaces)
25504 .take(ORDERED_LIST_MAX_MARKER_LEN)
25505 .collect();
25506
25507 for ordered_config in language.ordered_list() {
25508 let regex = match Regex::new(&ordered_config.pattern) {
25509 Ok(r) => r,
25510 Err(_) => continue,
25511 };
25512
25513 if let Some(captures) = regex.captures(&candidate) {
25514 let full_match = captures.get(0)?;
25515 let marker_len = full_match.len();
25516 let end_of_prefix = num_of_whitespaces + marker_len;
25517 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25518
25519 let has_content_after_marker = snapshot
25520 .chars_for_range(range)
25521 .skip(end_of_prefix)
25522 .any(|c| !c.is_whitespace());
25523
25524 if has_content_after_marker && cursor_is_after_prefix {
25525 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25526 let continuation = ordered_config
25527 .format
25528 .replace("{1}", &(number + 1).to_string());
25529 return Some(continuation.into());
25530 }
25531
25532 if start_point.column as usize == end_of_prefix {
25533 let continuation = ordered_config.format.replace("{1}", "1");
25534 if num_of_whitespaces == 0 {
25535 *newline_config = NewlineConfig::ClearCurrentLine;
25536 } else {
25537 *newline_config = NewlineConfig::UnindentCurrentLine {
25538 continuation: continuation.into(),
25539 };
25540 }
25541 }
25542
25543 return None;
25544 }
25545 }
25546
25547 None
25548}
25549
25550fn is_list_prefix_row(
25551 row: MultiBufferRow,
25552 buffer: &MultiBufferSnapshot,
25553 language: &LanguageScope,
25554) -> bool {
25555 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25556 return false;
25557 };
25558
25559 let num_of_whitespaces = snapshot
25560 .chars_for_range(range.clone())
25561 .take_while(|c| c.is_whitespace())
25562 .count();
25563
25564 let task_list_prefixes: Vec<_> = language
25565 .task_list()
25566 .into_iter()
25567 .flat_map(|config| {
25568 config
25569 .prefixes
25570 .iter()
25571 .map(|p| p.as_ref())
25572 .collect::<Vec<_>>()
25573 })
25574 .collect();
25575 let unordered_list_markers: Vec<_> = language
25576 .unordered_list()
25577 .iter()
25578 .map(|marker| marker.as_ref())
25579 .collect();
25580 let all_prefixes: Vec<_> = task_list_prefixes
25581 .into_iter()
25582 .chain(unordered_list_markers)
25583 .collect();
25584 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25585 let candidate: String = snapshot
25586 .chars_for_range(range.clone())
25587 .skip(num_of_whitespaces)
25588 .take(max_prefix_len)
25589 .collect();
25590 if all_prefixes
25591 .iter()
25592 .any(|prefix| candidate.starts_with(*prefix))
25593 {
25594 return true;
25595 }
25596 }
25597
25598 let ordered_list_candidate: String = snapshot
25599 .chars_for_range(range)
25600 .skip(num_of_whitespaces)
25601 .take(ORDERED_LIST_MAX_MARKER_LEN)
25602 .collect();
25603 for ordered_config in language.ordered_list() {
25604 let regex = match Regex::new(&ordered_config.pattern) {
25605 Ok(r) => r,
25606 Err(_) => continue,
25607 };
25608 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25609 return captures.get(0).is_some();
25610 }
25611 }
25612
25613 false
25614}
25615
25616#[derive(Debug)]
25617enum NewlineConfig {
25618 /// Insert newline with optional additional indent and optional extra blank line
25619 Newline {
25620 additional_indent: IndentSize,
25621 extra_line_additional_indent: Option<IndentSize>,
25622 prevent_auto_indent: bool,
25623 },
25624 /// Clear the current line
25625 ClearCurrentLine,
25626 /// Unindent the current line and add continuation
25627 UnindentCurrentLine { continuation: Arc<str> },
25628}
25629
25630impl NewlineConfig {
25631 fn has_extra_line(&self) -> bool {
25632 matches!(
25633 self,
25634 Self::Newline {
25635 extra_line_additional_indent: Some(_),
25636 ..
25637 }
25638 )
25639 }
25640
25641 fn insert_extra_newline_brackets(
25642 buffer: &MultiBufferSnapshot,
25643 range: Range<MultiBufferOffset>,
25644 language: &language::LanguageScope,
25645 ) -> bool {
25646 let leading_whitespace_len = buffer
25647 .reversed_chars_at(range.start)
25648 .take_while(|c| c.is_whitespace() && *c != '\n')
25649 .map(|c| c.len_utf8())
25650 .sum::<usize>();
25651 let trailing_whitespace_len = buffer
25652 .chars_at(range.end)
25653 .take_while(|c| c.is_whitespace() && *c != '\n')
25654 .map(|c| c.len_utf8())
25655 .sum::<usize>();
25656 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25657
25658 language.brackets().any(|(pair, enabled)| {
25659 let pair_start = pair.start.trim_end();
25660 let pair_end = pair.end.trim_start();
25661
25662 enabled
25663 && pair.newline
25664 && buffer.contains_str_at(range.end, pair_end)
25665 && buffer.contains_str_at(
25666 range.start.saturating_sub_usize(pair_start.len()),
25667 pair_start,
25668 )
25669 })
25670 }
25671
25672 fn insert_extra_newline_tree_sitter(
25673 buffer: &MultiBufferSnapshot,
25674 range: Range<MultiBufferOffset>,
25675 ) -> bool {
25676 let (buffer, range) = match buffer
25677 .range_to_buffer_ranges(range.start..=range.end)
25678 .as_slice()
25679 {
25680 [(buffer, range, _)] => (*buffer, range.clone()),
25681 _ => return false,
25682 };
25683 let pair = {
25684 let mut result: Option<BracketMatch<usize>> = None;
25685
25686 for pair in buffer
25687 .all_bracket_ranges(range.start.0..range.end.0)
25688 .filter(move |pair| {
25689 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
25690 })
25691 {
25692 let len = pair.close_range.end - pair.open_range.start;
25693
25694 if let Some(existing) = &result {
25695 let existing_len = existing.close_range.end - existing.open_range.start;
25696 if len > existing_len {
25697 continue;
25698 }
25699 }
25700
25701 result = Some(pair);
25702 }
25703
25704 result
25705 };
25706 let Some(pair) = pair else {
25707 return false;
25708 };
25709 pair.newline_only
25710 && buffer
25711 .chars_for_range(pair.open_range.end..range.start.0)
25712 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
25713 .all(|c| c.is_whitespace() && c != '\n')
25714 }
25715}
25716
25717fn update_uncommitted_diff_for_buffer(
25718 editor: Entity<Editor>,
25719 project: &Entity<Project>,
25720 buffers: impl IntoIterator<Item = Entity<Buffer>>,
25721 buffer: Entity<MultiBuffer>,
25722 cx: &mut App,
25723) -> Task<()> {
25724 let mut tasks = Vec::new();
25725 project.update(cx, |project, cx| {
25726 for buffer in buffers {
25727 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
25728 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
25729 }
25730 }
25731 });
25732 cx.spawn(async move |cx| {
25733 let diffs = future::join_all(tasks).await;
25734 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
25735 return;
25736 }
25737
25738 buffer.update(cx, |buffer, cx| {
25739 for diff in diffs.into_iter().flatten() {
25740 buffer.add_diff(diff, cx);
25741 }
25742 });
25743 })
25744}
25745
25746fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
25747 let tab_size = tab_size.get() as usize;
25748 let mut width = offset;
25749
25750 for ch in text.chars() {
25751 width += if ch == '\t' {
25752 tab_size - (width % tab_size)
25753 } else {
25754 1
25755 };
25756 }
25757
25758 width - offset
25759}
25760
25761#[cfg(test)]
25762mod tests {
25763 use super::*;
25764
25765 #[test]
25766 fn test_string_size_with_expanded_tabs() {
25767 let nz = |val| NonZeroU32::new(val).unwrap();
25768 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
25769 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
25770 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
25771 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
25772 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
25773 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
25774 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
25775 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
25776 }
25777}
25778
25779/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
25780struct WordBreakingTokenizer<'a> {
25781 input: &'a str,
25782}
25783
25784impl<'a> WordBreakingTokenizer<'a> {
25785 fn new(input: &'a str) -> Self {
25786 Self { input }
25787 }
25788}
25789
25790fn is_char_ideographic(ch: char) -> bool {
25791 use unicode_script::Script::*;
25792 use unicode_script::UnicodeScript;
25793 matches!(ch.script(), Han | Tangut | Yi)
25794}
25795
25796fn is_grapheme_ideographic(text: &str) -> bool {
25797 text.chars().any(is_char_ideographic)
25798}
25799
25800fn is_grapheme_whitespace(text: &str) -> bool {
25801 text.chars().any(|x| x.is_whitespace())
25802}
25803
25804fn should_stay_with_preceding_ideograph(text: &str) -> bool {
25805 text.chars()
25806 .next()
25807 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
25808}
25809
25810#[derive(PartialEq, Eq, Debug, Clone, Copy)]
25811enum WordBreakToken<'a> {
25812 Word { token: &'a str, grapheme_len: usize },
25813 InlineWhitespace { token: &'a str, grapheme_len: usize },
25814 Newline,
25815}
25816
25817impl<'a> Iterator for WordBreakingTokenizer<'a> {
25818 /// Yields a span, the count of graphemes in the token, and whether it was
25819 /// whitespace. Note that it also breaks at word boundaries.
25820 type Item = WordBreakToken<'a>;
25821
25822 fn next(&mut self) -> Option<Self::Item> {
25823 use unicode_segmentation::UnicodeSegmentation;
25824 if self.input.is_empty() {
25825 return None;
25826 }
25827
25828 let mut iter = self.input.graphemes(true).peekable();
25829 let mut offset = 0;
25830 let mut grapheme_len = 0;
25831 if let Some(first_grapheme) = iter.next() {
25832 let is_newline = first_grapheme == "\n";
25833 let is_whitespace = is_grapheme_whitespace(first_grapheme);
25834 offset += first_grapheme.len();
25835 grapheme_len += 1;
25836 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
25837 if let Some(grapheme) = iter.peek().copied()
25838 && should_stay_with_preceding_ideograph(grapheme)
25839 {
25840 offset += grapheme.len();
25841 grapheme_len += 1;
25842 }
25843 } else {
25844 let mut words = self.input[offset..].split_word_bound_indices().peekable();
25845 let mut next_word_bound = words.peek().copied();
25846 if next_word_bound.is_some_and(|(i, _)| i == 0) {
25847 next_word_bound = words.next();
25848 }
25849 while let Some(grapheme) = iter.peek().copied() {
25850 if next_word_bound.is_some_and(|(i, _)| i == offset) {
25851 break;
25852 };
25853 if is_grapheme_whitespace(grapheme) != is_whitespace
25854 || (grapheme == "\n") != is_newline
25855 {
25856 break;
25857 };
25858 offset += grapheme.len();
25859 grapheme_len += 1;
25860 iter.next();
25861 }
25862 }
25863 let token = &self.input[..offset];
25864 self.input = &self.input[offset..];
25865 if token == "\n" {
25866 Some(WordBreakToken::Newline)
25867 } else if is_whitespace {
25868 Some(WordBreakToken::InlineWhitespace {
25869 token,
25870 grapheme_len,
25871 })
25872 } else {
25873 Some(WordBreakToken::Word {
25874 token,
25875 grapheme_len,
25876 })
25877 }
25878 } else {
25879 None
25880 }
25881 }
25882}
25883
25884#[test]
25885fn test_word_breaking_tokenizer() {
25886 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
25887 ("", &[]),
25888 (" ", &[whitespace(" ", 2)]),
25889 ("Ʒ", &[word("Ʒ", 1)]),
25890 ("Ǽ", &[word("Ǽ", 1)]),
25891 ("⋑", &[word("⋑", 1)]),
25892 ("⋑⋑", &[word("⋑⋑", 2)]),
25893 (
25894 "原理,进而",
25895 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
25896 ),
25897 (
25898 "hello world",
25899 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
25900 ),
25901 (
25902 "hello, world",
25903 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
25904 ),
25905 (
25906 " hello world",
25907 &[
25908 whitespace(" ", 2),
25909 word("hello", 5),
25910 whitespace(" ", 1),
25911 word("world", 5),
25912 ],
25913 ),
25914 (
25915 "这是什么 \n 钢笔",
25916 &[
25917 word("这", 1),
25918 word("是", 1),
25919 word("什", 1),
25920 word("么", 1),
25921 whitespace(" ", 1),
25922 newline(),
25923 whitespace(" ", 1),
25924 word("钢", 1),
25925 word("笔", 1),
25926 ],
25927 ),
25928 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
25929 ];
25930
25931 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
25932 WordBreakToken::Word {
25933 token,
25934 grapheme_len,
25935 }
25936 }
25937
25938 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
25939 WordBreakToken::InlineWhitespace {
25940 token,
25941 grapheme_len,
25942 }
25943 }
25944
25945 fn newline() -> WordBreakToken<'static> {
25946 WordBreakToken::Newline
25947 }
25948
25949 for (input, result) in tests {
25950 assert_eq!(
25951 WordBreakingTokenizer::new(input)
25952 .collect::<Vec<_>>()
25953 .as_slice(),
25954 *result,
25955 );
25956 }
25957}
25958
25959fn wrap_with_prefix(
25960 first_line_prefix: String,
25961 subsequent_lines_prefix: String,
25962 unwrapped_text: String,
25963 wrap_column: usize,
25964 tab_size: NonZeroU32,
25965 preserve_existing_whitespace: bool,
25966) -> String {
25967 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
25968 let subsequent_lines_prefix_len =
25969 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
25970 let mut wrapped_text = String::new();
25971 let mut current_line = first_line_prefix;
25972 let mut is_first_line = true;
25973
25974 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
25975 let mut current_line_len = first_line_prefix_len;
25976 let mut in_whitespace = false;
25977 for token in tokenizer {
25978 let have_preceding_whitespace = in_whitespace;
25979 match token {
25980 WordBreakToken::Word {
25981 token,
25982 grapheme_len,
25983 } => {
25984 in_whitespace = false;
25985 let current_prefix_len = if is_first_line {
25986 first_line_prefix_len
25987 } else {
25988 subsequent_lines_prefix_len
25989 };
25990 if current_line_len + grapheme_len > wrap_column
25991 && current_line_len != current_prefix_len
25992 {
25993 wrapped_text.push_str(current_line.trim_end());
25994 wrapped_text.push('\n');
25995 is_first_line = false;
25996 current_line = subsequent_lines_prefix.clone();
25997 current_line_len = subsequent_lines_prefix_len;
25998 }
25999 current_line.push_str(token);
26000 current_line_len += grapheme_len;
26001 }
26002 WordBreakToken::InlineWhitespace {
26003 mut token,
26004 mut grapheme_len,
26005 } => {
26006 in_whitespace = true;
26007 if have_preceding_whitespace && !preserve_existing_whitespace {
26008 continue;
26009 }
26010 if !preserve_existing_whitespace {
26011 // Keep a single whitespace grapheme as-is
26012 if let Some(first) =
26013 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26014 {
26015 token = first;
26016 } else {
26017 token = " ";
26018 }
26019 grapheme_len = 1;
26020 }
26021 let current_prefix_len = if is_first_line {
26022 first_line_prefix_len
26023 } else {
26024 subsequent_lines_prefix_len
26025 };
26026 if current_line_len + grapheme_len > wrap_column {
26027 wrapped_text.push_str(current_line.trim_end());
26028 wrapped_text.push('\n');
26029 is_first_line = false;
26030 current_line = subsequent_lines_prefix.clone();
26031 current_line_len = subsequent_lines_prefix_len;
26032 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26033 current_line.push_str(token);
26034 current_line_len += grapheme_len;
26035 }
26036 }
26037 WordBreakToken::Newline => {
26038 in_whitespace = true;
26039 let current_prefix_len = if is_first_line {
26040 first_line_prefix_len
26041 } else {
26042 subsequent_lines_prefix_len
26043 };
26044 if preserve_existing_whitespace {
26045 wrapped_text.push_str(current_line.trim_end());
26046 wrapped_text.push('\n');
26047 is_first_line = false;
26048 current_line = subsequent_lines_prefix.clone();
26049 current_line_len = subsequent_lines_prefix_len;
26050 } else if have_preceding_whitespace {
26051 continue;
26052 } else if current_line_len + 1 > wrap_column
26053 && current_line_len != current_prefix_len
26054 {
26055 wrapped_text.push_str(current_line.trim_end());
26056 wrapped_text.push('\n');
26057 is_first_line = false;
26058 current_line = subsequent_lines_prefix.clone();
26059 current_line_len = subsequent_lines_prefix_len;
26060 } else if current_line_len != current_prefix_len {
26061 current_line.push(' ');
26062 current_line_len += 1;
26063 }
26064 }
26065 }
26066 }
26067
26068 if !current_line.is_empty() {
26069 wrapped_text.push_str(¤t_line);
26070 }
26071 wrapped_text
26072}
26073
26074#[test]
26075fn test_wrap_with_prefix() {
26076 assert_eq!(
26077 wrap_with_prefix(
26078 "# ".to_string(),
26079 "# ".to_string(),
26080 "abcdefg".to_string(),
26081 4,
26082 NonZeroU32::new(4).unwrap(),
26083 false,
26084 ),
26085 "# abcdefg"
26086 );
26087 assert_eq!(
26088 wrap_with_prefix(
26089 "".to_string(),
26090 "".to_string(),
26091 "\thello world".to_string(),
26092 8,
26093 NonZeroU32::new(4).unwrap(),
26094 false,
26095 ),
26096 "hello\nworld"
26097 );
26098 assert_eq!(
26099 wrap_with_prefix(
26100 "// ".to_string(),
26101 "// ".to_string(),
26102 "xx \nyy zz aa bb cc".to_string(),
26103 12,
26104 NonZeroU32::new(4).unwrap(),
26105 false,
26106 ),
26107 "// xx yy zz\n// aa bb cc"
26108 );
26109 assert_eq!(
26110 wrap_with_prefix(
26111 String::new(),
26112 String::new(),
26113 "这是什么 \n 钢笔".to_string(),
26114 3,
26115 NonZeroU32::new(4).unwrap(),
26116 false,
26117 ),
26118 "这是什\n么 钢\n笔"
26119 );
26120 assert_eq!(
26121 wrap_with_prefix(
26122 String::new(),
26123 String::new(),
26124 format!("foo{}bar", '\u{2009}'), // thin space
26125 80,
26126 NonZeroU32::new(4).unwrap(),
26127 false,
26128 ),
26129 format!("foo{}bar", '\u{2009}')
26130 );
26131}
26132
26133pub trait CollaborationHub {
26134 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26135 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26136 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26137}
26138
26139impl CollaborationHub for Entity<Project> {
26140 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26141 self.read(cx).collaborators()
26142 }
26143
26144 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26145 self.read(cx).user_store().read(cx).participant_indices()
26146 }
26147
26148 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26149 let this = self.read(cx);
26150 let user_ids = this.collaborators().values().map(|c| c.user_id);
26151 this.user_store().read(cx).participant_names(user_ids, cx)
26152 }
26153}
26154
26155pub trait SemanticsProvider {
26156 fn hover(
26157 &self,
26158 buffer: &Entity<Buffer>,
26159 position: text::Anchor,
26160 cx: &mut App,
26161 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26162
26163 fn inline_values(
26164 &self,
26165 buffer_handle: Entity<Buffer>,
26166 range: Range<text::Anchor>,
26167 cx: &mut App,
26168 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26169
26170 fn applicable_inlay_chunks(
26171 &self,
26172 buffer: &Entity<Buffer>,
26173 ranges: &[Range<text::Anchor>],
26174 cx: &mut App,
26175 ) -> Vec<Range<BufferRow>>;
26176
26177 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26178
26179 fn inlay_hints(
26180 &self,
26181 invalidate: InvalidationStrategy,
26182 buffer: Entity<Buffer>,
26183 ranges: Vec<Range<text::Anchor>>,
26184 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26185 cx: &mut App,
26186 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26187
26188 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26189
26190 fn document_highlights(
26191 &self,
26192 buffer: &Entity<Buffer>,
26193 position: text::Anchor,
26194 cx: &mut App,
26195 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26196
26197 fn definitions(
26198 &self,
26199 buffer: &Entity<Buffer>,
26200 position: text::Anchor,
26201 kind: GotoDefinitionKind,
26202 cx: &mut App,
26203 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26204
26205 fn range_for_rename(
26206 &self,
26207 buffer: &Entity<Buffer>,
26208 position: text::Anchor,
26209 cx: &mut App,
26210 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26211
26212 fn perform_rename(
26213 &self,
26214 buffer: &Entity<Buffer>,
26215 position: text::Anchor,
26216 new_name: String,
26217 cx: &mut App,
26218 ) -> Option<Task<Result<ProjectTransaction>>>;
26219}
26220
26221pub trait CompletionProvider {
26222 fn completions(
26223 &self,
26224 excerpt_id: ExcerptId,
26225 buffer: &Entity<Buffer>,
26226 buffer_position: text::Anchor,
26227 trigger: CompletionContext,
26228 window: &mut Window,
26229 cx: &mut Context<Editor>,
26230 ) -> Task<Result<Vec<CompletionResponse>>>;
26231
26232 fn resolve_completions(
26233 &self,
26234 _buffer: Entity<Buffer>,
26235 _completion_indices: Vec<usize>,
26236 _completions: Rc<RefCell<Box<[Completion]>>>,
26237 _cx: &mut Context<Editor>,
26238 ) -> Task<Result<bool>> {
26239 Task::ready(Ok(false))
26240 }
26241
26242 fn apply_additional_edits_for_completion(
26243 &self,
26244 _buffer: Entity<Buffer>,
26245 _completions: Rc<RefCell<Box<[Completion]>>>,
26246 _completion_index: usize,
26247 _push_to_history: bool,
26248 _cx: &mut Context<Editor>,
26249 ) -> Task<Result<Option<language::Transaction>>> {
26250 Task::ready(Ok(None))
26251 }
26252
26253 fn is_completion_trigger(
26254 &self,
26255 buffer: &Entity<Buffer>,
26256 position: language::Anchor,
26257 text: &str,
26258 trigger_in_words: bool,
26259 cx: &mut Context<Editor>,
26260 ) -> bool;
26261
26262 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26263
26264 fn sort_completions(&self) -> bool {
26265 true
26266 }
26267
26268 fn filter_completions(&self) -> bool {
26269 true
26270 }
26271
26272 fn show_snippets(&self) -> bool {
26273 false
26274 }
26275}
26276
26277pub trait CodeActionProvider {
26278 fn id(&self) -> Arc<str>;
26279
26280 fn code_actions(
26281 &self,
26282 buffer: &Entity<Buffer>,
26283 range: Range<text::Anchor>,
26284 window: &mut Window,
26285 cx: &mut App,
26286 ) -> Task<Result<Vec<CodeAction>>>;
26287
26288 fn apply_code_action(
26289 &self,
26290 buffer_handle: Entity<Buffer>,
26291 action: CodeAction,
26292 excerpt_id: ExcerptId,
26293 push_to_history: bool,
26294 window: &mut Window,
26295 cx: &mut App,
26296 ) -> Task<Result<ProjectTransaction>>;
26297}
26298
26299impl CodeActionProvider for Entity<Project> {
26300 fn id(&self) -> Arc<str> {
26301 "project".into()
26302 }
26303
26304 fn code_actions(
26305 &self,
26306 buffer: &Entity<Buffer>,
26307 range: Range<text::Anchor>,
26308 _window: &mut Window,
26309 cx: &mut App,
26310 ) -> Task<Result<Vec<CodeAction>>> {
26311 self.update(cx, |project, cx| {
26312 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26313 let code_actions = project.code_actions(buffer, range, None, cx);
26314 cx.background_spawn(async move {
26315 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26316 Ok(code_lens_actions
26317 .context("code lens fetch")?
26318 .into_iter()
26319 .flatten()
26320 .chain(
26321 code_actions
26322 .context("code action fetch")?
26323 .into_iter()
26324 .flatten(),
26325 )
26326 .collect())
26327 })
26328 })
26329 }
26330
26331 fn apply_code_action(
26332 &self,
26333 buffer_handle: Entity<Buffer>,
26334 action: CodeAction,
26335 _excerpt_id: ExcerptId,
26336 push_to_history: bool,
26337 _window: &mut Window,
26338 cx: &mut App,
26339 ) -> Task<Result<ProjectTransaction>> {
26340 self.update(cx, |project, cx| {
26341 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26342 })
26343 }
26344}
26345
26346fn snippet_completions(
26347 project: &Project,
26348 buffer: &Entity<Buffer>,
26349 buffer_anchor: text::Anchor,
26350 classifier: CharClassifier,
26351 cx: &mut App,
26352) -> Task<Result<CompletionResponse>> {
26353 let languages = buffer.read(cx).languages_at(buffer_anchor);
26354 let snippet_store = project.snippets().read(cx);
26355
26356 let scopes: Vec<_> = languages
26357 .iter()
26358 .filter_map(|language| {
26359 let language_name = language.lsp_id();
26360 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26361
26362 if snippets.is_empty() {
26363 None
26364 } else {
26365 Some((language.default_scope(), snippets))
26366 }
26367 })
26368 .collect();
26369
26370 if scopes.is_empty() {
26371 return Task::ready(Ok(CompletionResponse {
26372 completions: vec![],
26373 display_options: CompletionDisplayOptions::default(),
26374 is_incomplete: false,
26375 }));
26376 }
26377
26378 let snapshot = buffer.read(cx).text_snapshot();
26379 let executor = cx.background_executor().clone();
26380
26381 cx.background_spawn(async move {
26382 let is_word_char = |c| classifier.is_word(c);
26383
26384 let mut is_incomplete = false;
26385 let mut completions: Vec<Completion> = Vec::new();
26386
26387 const MAX_PREFIX_LEN: usize = 128;
26388 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26389 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26390 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26391
26392 let max_buffer_window: String = snapshot
26393 .text_for_range(window_start..buffer_offset)
26394 .collect();
26395
26396 if max_buffer_window.is_empty() {
26397 return Ok(CompletionResponse {
26398 completions: vec![],
26399 display_options: CompletionDisplayOptions::default(),
26400 is_incomplete: true,
26401 });
26402 }
26403
26404 for (_scope, snippets) in scopes.into_iter() {
26405 // Sort snippets by word count to match longer snippet prefixes first.
26406 let mut sorted_snippet_candidates = snippets
26407 .iter()
26408 .enumerate()
26409 .flat_map(|(snippet_ix, snippet)| {
26410 snippet
26411 .prefix
26412 .iter()
26413 .enumerate()
26414 .map(move |(prefix_ix, prefix)| {
26415 let word_count =
26416 snippet_candidate_suffixes(prefix, is_word_char).count();
26417 ((snippet_ix, prefix_ix), prefix, word_count)
26418 })
26419 })
26420 .collect_vec();
26421 sorted_snippet_candidates
26422 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26423
26424 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26425
26426 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26427 .take(
26428 sorted_snippet_candidates
26429 .first()
26430 .map(|(_, _, word_count)| *word_count)
26431 .unwrap_or_default(),
26432 )
26433 .collect_vec();
26434
26435 const MAX_RESULTS: usize = 100;
26436 // Each match also remembers how many characters from the buffer it consumed
26437 let mut matches: Vec<(StringMatch, usize)> = vec![];
26438
26439 let mut snippet_list_cutoff_index = 0;
26440 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26441 let word_count = buffer_index + 1;
26442 // Increase `snippet_list_cutoff_index` until we have all of the
26443 // snippets with sufficiently many words.
26444 while sorted_snippet_candidates
26445 .get(snippet_list_cutoff_index)
26446 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26447 *snippet_word_count >= word_count
26448 })
26449 {
26450 snippet_list_cutoff_index += 1;
26451 }
26452
26453 // Take only the candidates with at least `word_count` many words
26454 let snippet_candidates_at_word_len =
26455 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26456
26457 let candidates = snippet_candidates_at_word_len
26458 .iter()
26459 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26460 .enumerate() // index in `sorted_snippet_candidates`
26461 // First char must match
26462 .filter(|(_ix, prefix)| {
26463 itertools::equal(
26464 prefix
26465 .chars()
26466 .next()
26467 .into_iter()
26468 .flat_map(|c| c.to_lowercase()),
26469 buffer_window
26470 .chars()
26471 .next()
26472 .into_iter()
26473 .flat_map(|c| c.to_lowercase()),
26474 )
26475 })
26476 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26477 .collect::<Vec<StringMatchCandidate>>();
26478
26479 matches.extend(
26480 fuzzy::match_strings(
26481 &candidates,
26482 &buffer_window,
26483 buffer_window.chars().any(|c| c.is_uppercase()),
26484 true,
26485 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26486 &Default::default(),
26487 executor.clone(),
26488 )
26489 .await
26490 .into_iter()
26491 .map(|string_match| (string_match, buffer_window.len())),
26492 );
26493
26494 if matches.len() >= MAX_RESULTS {
26495 break;
26496 }
26497 }
26498
26499 let to_lsp = |point: &text::Anchor| {
26500 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26501 point_to_lsp(end)
26502 };
26503 let lsp_end = to_lsp(&buffer_anchor);
26504
26505 if matches.len() >= MAX_RESULTS {
26506 is_incomplete = true;
26507 }
26508
26509 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26510 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26511 sorted_snippet_candidates[string_match.candidate_id];
26512 let snippet = &snippets[snippet_index];
26513 let start = buffer_offset - buffer_window_len;
26514 let start = snapshot.anchor_before(start);
26515 let range = start..buffer_anchor;
26516 let lsp_start = to_lsp(&start);
26517 let lsp_range = lsp::Range {
26518 start: lsp_start,
26519 end: lsp_end,
26520 };
26521 Completion {
26522 replace_range: range,
26523 new_text: snippet.body.clone(),
26524 source: CompletionSource::Lsp {
26525 insert_range: None,
26526 server_id: LanguageServerId(usize::MAX),
26527 resolved: true,
26528 lsp_completion: Box::new(lsp::CompletionItem {
26529 label: snippet.prefix.first().unwrap().clone(),
26530 kind: Some(CompletionItemKind::SNIPPET),
26531 label_details: snippet.description.as_ref().map(|description| {
26532 lsp::CompletionItemLabelDetails {
26533 detail: Some(description.clone()),
26534 description: None,
26535 }
26536 }),
26537 insert_text_format: Some(InsertTextFormat::SNIPPET),
26538 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26539 lsp::InsertReplaceEdit {
26540 new_text: snippet.body.clone(),
26541 insert: lsp_range,
26542 replace: lsp_range,
26543 },
26544 )),
26545 filter_text: Some(snippet.body.clone()),
26546 sort_text: Some(char::MAX.to_string()),
26547 ..lsp::CompletionItem::default()
26548 }),
26549 lsp_defaults: None,
26550 },
26551 label: CodeLabel {
26552 text: matching_prefix.clone(),
26553 runs: Vec::new(),
26554 filter_range: 0..matching_prefix.len(),
26555 },
26556 icon_path: None,
26557 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26558 single_line: snippet.name.clone().into(),
26559 plain_text: snippet
26560 .description
26561 .clone()
26562 .map(|description| description.into()),
26563 }),
26564 insert_text_mode: None,
26565 confirm: None,
26566 match_start: Some(start),
26567 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26568 }
26569 }));
26570 }
26571
26572 Ok(CompletionResponse {
26573 completions,
26574 display_options: CompletionDisplayOptions::default(),
26575 is_incomplete,
26576 })
26577 })
26578}
26579
26580impl CompletionProvider for Entity<Project> {
26581 fn completions(
26582 &self,
26583 _excerpt_id: ExcerptId,
26584 buffer: &Entity<Buffer>,
26585 buffer_position: text::Anchor,
26586 options: CompletionContext,
26587 _window: &mut Window,
26588 cx: &mut Context<Editor>,
26589 ) -> Task<Result<Vec<CompletionResponse>>> {
26590 self.update(cx, |project, cx| {
26591 let task = project.completions(buffer, buffer_position, options, cx);
26592 cx.background_spawn(task)
26593 })
26594 }
26595
26596 fn resolve_completions(
26597 &self,
26598 buffer: Entity<Buffer>,
26599 completion_indices: Vec<usize>,
26600 completions: Rc<RefCell<Box<[Completion]>>>,
26601 cx: &mut Context<Editor>,
26602 ) -> Task<Result<bool>> {
26603 self.update(cx, |project, cx| {
26604 project.lsp_store().update(cx, |lsp_store, cx| {
26605 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26606 })
26607 })
26608 }
26609
26610 fn apply_additional_edits_for_completion(
26611 &self,
26612 buffer: Entity<Buffer>,
26613 completions: Rc<RefCell<Box<[Completion]>>>,
26614 completion_index: usize,
26615 push_to_history: bool,
26616 cx: &mut Context<Editor>,
26617 ) -> Task<Result<Option<language::Transaction>>> {
26618 self.update(cx, |project, cx| {
26619 project.lsp_store().update(cx, |lsp_store, cx| {
26620 lsp_store.apply_additional_edits_for_completion(
26621 buffer,
26622 completions,
26623 completion_index,
26624 push_to_history,
26625 cx,
26626 )
26627 })
26628 })
26629 }
26630
26631 fn is_completion_trigger(
26632 &self,
26633 buffer: &Entity<Buffer>,
26634 position: language::Anchor,
26635 text: &str,
26636 trigger_in_words: bool,
26637 cx: &mut Context<Editor>,
26638 ) -> bool {
26639 let mut chars = text.chars();
26640 let char = if let Some(char) = chars.next() {
26641 char
26642 } else {
26643 return false;
26644 };
26645 if chars.next().is_some() {
26646 return false;
26647 }
26648
26649 let buffer = buffer.read(cx);
26650 let snapshot = buffer.snapshot();
26651 let classifier = snapshot
26652 .char_classifier_at(position)
26653 .scope_context(Some(CharScopeContext::Completion));
26654 if trigger_in_words && classifier.is_word(char) {
26655 return true;
26656 }
26657
26658 buffer.completion_triggers().contains(text)
26659 }
26660
26661 fn show_snippets(&self) -> bool {
26662 true
26663 }
26664}
26665
26666impl SemanticsProvider for Entity<Project> {
26667 fn hover(
26668 &self,
26669 buffer: &Entity<Buffer>,
26670 position: text::Anchor,
26671 cx: &mut App,
26672 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26673 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
26674 }
26675
26676 fn document_highlights(
26677 &self,
26678 buffer: &Entity<Buffer>,
26679 position: text::Anchor,
26680 cx: &mut App,
26681 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
26682 Some(self.update(cx, |project, cx| {
26683 project.document_highlights(buffer, position, cx)
26684 }))
26685 }
26686
26687 fn definitions(
26688 &self,
26689 buffer: &Entity<Buffer>,
26690 position: text::Anchor,
26691 kind: GotoDefinitionKind,
26692 cx: &mut App,
26693 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
26694 Some(self.update(cx, |project, cx| match kind {
26695 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
26696 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
26697 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
26698 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
26699 }))
26700 }
26701
26702 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
26703 self.update(cx, |project, cx| {
26704 if project
26705 .active_debug_session(cx)
26706 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
26707 {
26708 return true;
26709 }
26710
26711 buffer.update(cx, |buffer, cx| {
26712 project.any_language_server_supports_inlay_hints(buffer, cx)
26713 })
26714 })
26715 }
26716
26717 fn inline_values(
26718 &self,
26719 buffer_handle: Entity<Buffer>,
26720 range: Range<text::Anchor>,
26721 cx: &mut App,
26722 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
26723 self.update(cx, |project, cx| {
26724 let (session, active_stack_frame) = project.active_debug_session(cx)?;
26725
26726 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
26727 })
26728 }
26729
26730 fn applicable_inlay_chunks(
26731 &self,
26732 buffer: &Entity<Buffer>,
26733 ranges: &[Range<text::Anchor>],
26734 cx: &mut App,
26735 ) -> Vec<Range<BufferRow>> {
26736 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26737 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
26738 })
26739 }
26740
26741 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
26742 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
26743 lsp_store.invalidate_inlay_hints(for_buffers)
26744 });
26745 }
26746
26747 fn inlay_hints(
26748 &self,
26749 invalidate: InvalidationStrategy,
26750 buffer: Entity<Buffer>,
26751 ranges: Vec<Range<text::Anchor>>,
26752 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26753 cx: &mut App,
26754 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
26755 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26756 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
26757 }))
26758 }
26759
26760 fn range_for_rename(
26761 &self,
26762 buffer: &Entity<Buffer>,
26763 position: text::Anchor,
26764 cx: &mut App,
26765 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
26766 Some(self.update(cx, |project, cx| {
26767 let buffer = buffer.clone();
26768 let task = project.prepare_rename(buffer.clone(), position, cx);
26769 cx.spawn(async move |_, cx| {
26770 Ok(match task.await? {
26771 PrepareRenameResponse::Success(range) => Some(range),
26772 PrepareRenameResponse::InvalidPosition => None,
26773 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
26774 // Fallback on using TreeSitter info to determine identifier range
26775 buffer.read_with(cx, |buffer, _| {
26776 let snapshot = buffer.snapshot();
26777 let (range, kind) = snapshot.surrounding_word(position, None);
26778 if kind != Some(CharKind::Word) {
26779 return None;
26780 }
26781 Some(
26782 snapshot.anchor_before(range.start)
26783 ..snapshot.anchor_after(range.end),
26784 )
26785 })
26786 }
26787 })
26788 })
26789 }))
26790 }
26791
26792 fn perform_rename(
26793 &self,
26794 buffer: &Entity<Buffer>,
26795 position: text::Anchor,
26796 new_name: String,
26797 cx: &mut App,
26798 ) -> Option<Task<Result<ProjectTransaction>>> {
26799 Some(self.update(cx, |project, cx| {
26800 project.perform_rename(buffer.clone(), position, new_name, cx)
26801 }))
26802 }
26803}
26804
26805fn consume_contiguous_rows(
26806 contiguous_row_selections: &mut Vec<Selection<Point>>,
26807 selection: &Selection<Point>,
26808 display_map: &DisplaySnapshot,
26809 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
26810) -> (MultiBufferRow, MultiBufferRow) {
26811 contiguous_row_selections.push(selection.clone());
26812 let start_row = starting_row(selection, display_map);
26813 let mut end_row = ending_row(selection, display_map);
26814
26815 while let Some(next_selection) = selections.peek() {
26816 if next_selection.start.row <= end_row.0 {
26817 end_row = ending_row(next_selection, display_map);
26818 contiguous_row_selections.push(selections.next().unwrap().clone());
26819 } else {
26820 break;
26821 }
26822 }
26823 (start_row, end_row)
26824}
26825
26826fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26827 if selection.start.column > 0 {
26828 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
26829 } else {
26830 MultiBufferRow(selection.start.row)
26831 }
26832}
26833
26834fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26835 if next_selection.end.column > 0 || next_selection.is_empty() {
26836 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
26837 } else {
26838 MultiBufferRow(next_selection.end.row)
26839 }
26840}
26841
26842impl EditorSnapshot {
26843 pub fn remote_selections_in_range<'a>(
26844 &'a self,
26845 range: &'a Range<Anchor>,
26846 collaboration_hub: &dyn CollaborationHub,
26847 cx: &'a App,
26848 ) -> impl 'a + Iterator<Item = RemoteSelection> {
26849 let participant_names = collaboration_hub.user_names(cx);
26850 let participant_indices = collaboration_hub.user_participant_indices(cx);
26851 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
26852 let collaborators_by_replica_id = collaborators_by_peer_id
26853 .values()
26854 .map(|collaborator| (collaborator.replica_id, collaborator))
26855 .collect::<HashMap<_, _>>();
26856 self.buffer_snapshot()
26857 .selections_in_range(range, false)
26858 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
26859 if replica_id == ReplicaId::AGENT {
26860 Some(RemoteSelection {
26861 replica_id,
26862 selection,
26863 cursor_shape,
26864 line_mode,
26865 collaborator_id: CollaboratorId::Agent,
26866 user_name: Some("Agent".into()),
26867 color: cx.theme().players().agent(),
26868 })
26869 } else {
26870 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
26871 let participant_index = participant_indices.get(&collaborator.user_id).copied();
26872 let user_name = participant_names.get(&collaborator.user_id).cloned();
26873 Some(RemoteSelection {
26874 replica_id,
26875 selection,
26876 cursor_shape,
26877 line_mode,
26878 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
26879 user_name,
26880 color: if let Some(index) = participant_index {
26881 cx.theme().players().color_for_participant(index.0)
26882 } else {
26883 cx.theme().players().absent()
26884 },
26885 })
26886 }
26887 })
26888 }
26889
26890 pub fn hunks_for_ranges(
26891 &self,
26892 ranges: impl IntoIterator<Item = Range<Point>>,
26893 ) -> Vec<MultiBufferDiffHunk> {
26894 let mut hunks = Vec::new();
26895 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
26896 HashMap::default();
26897 for query_range in ranges {
26898 let query_rows =
26899 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
26900 for hunk in self.buffer_snapshot().diff_hunks_in_range(
26901 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
26902 ) {
26903 // Include deleted hunks that are adjacent to the query range, because
26904 // otherwise they would be missed.
26905 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
26906 if hunk.status().is_deleted() {
26907 intersects_range |= hunk.row_range.start == query_rows.end;
26908 intersects_range |= hunk.row_range.end == query_rows.start;
26909 }
26910 if intersects_range {
26911 if !processed_buffer_rows
26912 .entry(hunk.buffer_id)
26913 .or_default()
26914 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
26915 {
26916 continue;
26917 }
26918 hunks.push(hunk);
26919 }
26920 }
26921 }
26922
26923 hunks
26924 }
26925
26926 fn display_diff_hunks_for_rows<'a>(
26927 &'a self,
26928 display_rows: Range<DisplayRow>,
26929 folded_buffers: &'a HashSet<BufferId>,
26930 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
26931 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
26932 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
26933
26934 self.buffer_snapshot()
26935 .diff_hunks_in_range(buffer_start..buffer_end)
26936 .filter_map(|hunk| {
26937 if folded_buffers.contains(&hunk.buffer_id) {
26938 return None;
26939 }
26940
26941 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
26942 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
26943
26944 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
26945 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
26946
26947 let display_hunk = if hunk_display_start.column() != 0 {
26948 DisplayDiffHunk::Folded {
26949 display_row: hunk_display_start.row(),
26950 }
26951 } else {
26952 let mut end_row = hunk_display_end.row();
26953 if hunk_display_end.column() > 0 {
26954 end_row.0 += 1;
26955 }
26956 let is_created_file = hunk.is_created_file();
26957
26958 DisplayDiffHunk::Unfolded {
26959 status: hunk.status(),
26960 diff_base_byte_range: hunk.diff_base_byte_range.start.0
26961 ..hunk.diff_base_byte_range.end.0,
26962 word_diffs: hunk.word_diffs,
26963 display_row_range: hunk_display_start.row()..end_row,
26964 multi_buffer_range: Anchor::range_in_buffer(
26965 hunk.excerpt_id,
26966 hunk.buffer_range,
26967 ),
26968 is_created_file,
26969 }
26970 };
26971
26972 Some(display_hunk)
26973 })
26974 }
26975
26976 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
26977 self.display_snapshot
26978 .buffer_snapshot()
26979 .language_at(position)
26980 }
26981
26982 pub fn is_focused(&self) -> bool {
26983 self.is_focused
26984 }
26985
26986 pub fn placeholder_text(&self) -> Option<String> {
26987 self.placeholder_display_snapshot
26988 .as_ref()
26989 .map(|display_map| display_map.text())
26990 }
26991
26992 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
26993 self.scroll_anchor.scroll_position(&self.display_snapshot)
26994 }
26995
26996 pub fn gutter_dimensions(
26997 &self,
26998 font_id: FontId,
26999 font_size: Pixels,
27000 style: &EditorStyle,
27001 window: &mut Window,
27002 cx: &App,
27003 ) -> GutterDimensions {
27004 if self.show_gutter
27005 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27006 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27007 {
27008 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27009 matches!(
27010 ProjectSettings::get_global(cx).git.git_gutter,
27011 GitGutterSetting::TrackedFiles
27012 )
27013 });
27014 let gutter_settings = EditorSettings::get_global(cx).gutter;
27015 let show_line_numbers = self
27016 .show_line_numbers
27017 .unwrap_or(gutter_settings.line_numbers);
27018 let line_gutter_width = if show_line_numbers {
27019 // Avoid flicker-like gutter resizes when the line number gains another digit by
27020 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27021 let min_width_for_number_on_gutter =
27022 ch_advance * gutter_settings.min_line_number_digits as f32;
27023 self.max_line_number_width(style, window)
27024 .max(min_width_for_number_on_gutter)
27025 } else {
27026 0.0.into()
27027 };
27028
27029 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27030 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27031
27032 let git_blame_entries_width =
27033 self.git_blame_gutter_max_author_length
27034 .map(|max_author_length| {
27035 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27036 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27037
27038 /// The number of characters to dedicate to gaps and margins.
27039 const SPACING_WIDTH: usize = 4;
27040
27041 let max_char_count = max_author_length.min(renderer.max_author_length())
27042 + ::git::SHORT_SHA_LENGTH
27043 + MAX_RELATIVE_TIMESTAMP.len()
27044 + SPACING_WIDTH;
27045
27046 ch_advance * max_char_count
27047 });
27048
27049 let is_singleton = self.buffer_snapshot().is_singleton();
27050
27051 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27052 left_padding += if !is_singleton {
27053 ch_width * 4.0
27054 } else if show_runnables || show_breakpoints {
27055 ch_width * 3.0
27056 } else if show_git_gutter && show_line_numbers {
27057 ch_width * 2.0
27058 } else if show_git_gutter || show_line_numbers {
27059 ch_width
27060 } else {
27061 px(0.)
27062 };
27063
27064 let shows_folds = is_singleton && gutter_settings.folds;
27065
27066 let right_padding = if shows_folds && show_line_numbers {
27067 ch_width * 4.0
27068 } else if shows_folds || (!is_singleton && show_line_numbers) {
27069 ch_width * 3.0
27070 } else if show_line_numbers {
27071 ch_width
27072 } else {
27073 px(0.)
27074 };
27075
27076 GutterDimensions {
27077 left_padding,
27078 right_padding,
27079 width: line_gutter_width + left_padding + right_padding,
27080 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27081 git_blame_entries_width,
27082 }
27083 } else if self.offset_content {
27084 GutterDimensions::default_with_margin(font_id, font_size, cx)
27085 } else {
27086 GutterDimensions::default()
27087 }
27088 }
27089
27090 pub fn render_crease_toggle(
27091 &self,
27092 buffer_row: MultiBufferRow,
27093 row_contains_cursor: bool,
27094 editor: Entity<Editor>,
27095 window: &mut Window,
27096 cx: &mut App,
27097 ) -> Option<AnyElement> {
27098 let folded = self.is_line_folded(buffer_row);
27099 let mut is_foldable = false;
27100
27101 if let Some(crease) = self
27102 .crease_snapshot
27103 .query_row(buffer_row, self.buffer_snapshot())
27104 {
27105 is_foldable = true;
27106 match crease {
27107 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27108 if let Some(render_toggle) = render_toggle {
27109 let toggle_callback =
27110 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27111 if folded {
27112 editor.update(cx, |editor, cx| {
27113 editor.fold_at(buffer_row, window, cx)
27114 });
27115 } else {
27116 editor.update(cx, |editor, cx| {
27117 editor.unfold_at(buffer_row, window, cx)
27118 });
27119 }
27120 });
27121 return Some((render_toggle)(
27122 buffer_row,
27123 folded,
27124 toggle_callback,
27125 window,
27126 cx,
27127 ));
27128 }
27129 }
27130 }
27131 }
27132
27133 is_foldable |= self.starts_indent(buffer_row);
27134
27135 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27136 Some(
27137 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27138 .toggle_state(folded)
27139 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27140 if folded {
27141 this.unfold_at(buffer_row, window, cx);
27142 } else {
27143 this.fold_at(buffer_row, window, cx);
27144 }
27145 }))
27146 .into_any_element(),
27147 )
27148 } else {
27149 None
27150 }
27151 }
27152
27153 pub fn render_crease_trailer(
27154 &self,
27155 buffer_row: MultiBufferRow,
27156 window: &mut Window,
27157 cx: &mut App,
27158 ) -> Option<AnyElement> {
27159 let folded = self.is_line_folded(buffer_row);
27160 if let Crease::Inline { render_trailer, .. } = self
27161 .crease_snapshot
27162 .query_row(buffer_row, self.buffer_snapshot())?
27163 {
27164 let render_trailer = render_trailer.as_ref()?;
27165 Some(render_trailer(buffer_row, folded, window, cx))
27166 } else {
27167 None
27168 }
27169 }
27170
27171 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27172 let digit_count = self.widest_line_number().ilog10() + 1;
27173 column_pixels(style, digit_count as usize, window)
27174 }
27175
27176 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27177 ///
27178 /// This is positive if `base` is before `line`.
27179 fn relative_line_delta(
27180 &self,
27181 current_selection_head: DisplayRow,
27182 first_visible_row: DisplayRow,
27183 consider_wrapped_lines: bool,
27184 ) -> i64 {
27185 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27186 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27187
27188 if consider_wrapped_lines {
27189 let wrap_snapshot = self.wrap_snapshot();
27190 let base_wrap_row = wrap_snapshot
27191 .make_wrap_point(current_selection_head, Bias::Left)
27192 .row();
27193 let wrap_row = wrap_snapshot
27194 .make_wrap_point(first_visible_row, Bias::Left)
27195 .row();
27196
27197 wrap_row.0 as i64 - base_wrap_row.0 as i64
27198 } else {
27199 let fold_snapshot = self.fold_snapshot();
27200 let base_fold_row = fold_snapshot
27201 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27202 .row();
27203 let fold_row = fold_snapshot
27204 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27205 .row();
27206
27207 fold_row as i64 - base_fold_row as i64
27208 }
27209 }
27210
27211 /// Returns the unsigned relative line number to display for each row in `rows`.
27212 ///
27213 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27214 pub fn calculate_relative_line_numbers(
27215 &self,
27216 rows: &Range<DisplayRow>,
27217 current_selection_head: DisplayRow,
27218 count_wrapped_lines: bool,
27219 ) -> HashMap<DisplayRow, u32> {
27220 let initial_offset =
27221 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27222 let current_selection_point = current_selection_head.as_display_point().to_point(self);
27223
27224 self.row_infos(rows.start)
27225 .take(rows.len())
27226 .enumerate()
27227 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27228 .filter(|(_row, row_info)| {
27229 row_info.buffer_row.is_some()
27230 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27231 })
27232 .enumerate()
27233 .filter(|(_, (row, row_info))| {
27234 // We want to check here that
27235 // - the row is not the current selection head to ensure the current
27236 // line has absolute numbering
27237 // - similarly, should the selection head live in a soft-wrapped line
27238 // and we are not counting those, that the parent line keeps its
27239 // absolute number
27240 // - lastly, if we are in a deleted line, it is fine to number this
27241 // relative with 0, as otherwise it would have no line number at all
27242 (*row != current_selection_head
27243 && (count_wrapped_lines
27244 || row_info.buffer_row != Some(current_selection_point.row)))
27245 || row_info
27246 .diff_status
27247 .is_some_and(|status| status.is_deleted())
27248 })
27249 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
27250 .collect()
27251 }
27252}
27253
27254pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27255 let font_size = style.text.font_size.to_pixels(window.rem_size());
27256 let layout = window.text_system().shape_line(
27257 SharedString::from(" ".repeat(column)),
27258 font_size,
27259 &[TextRun {
27260 len: column,
27261 font: style.text.font(),
27262 color: Hsla::default(),
27263 ..Default::default()
27264 }],
27265 None,
27266 );
27267
27268 layout.width
27269}
27270
27271impl Deref for EditorSnapshot {
27272 type Target = DisplaySnapshot;
27273
27274 fn deref(&self) -> &Self::Target {
27275 &self.display_snapshot
27276 }
27277}
27278
27279#[derive(Clone, Debug, PartialEq, Eq)]
27280pub enum EditorEvent {
27281 /// Emitted when the stored review comments change (added, removed, or updated).
27282 ReviewCommentsChanged {
27283 /// The new total count of review comments.
27284 total_count: usize,
27285 },
27286 InputIgnored {
27287 text: Arc<str>,
27288 },
27289 InputHandled {
27290 utf16_range_to_replace: Option<Range<isize>>,
27291 text: Arc<str>,
27292 },
27293 ExcerptsAdded {
27294 buffer: Entity<Buffer>,
27295 predecessor: ExcerptId,
27296 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27297 },
27298 ExcerptsRemoved {
27299 ids: Vec<ExcerptId>,
27300 removed_buffer_ids: Vec<BufferId>,
27301 },
27302 BufferFoldToggled {
27303 ids: Vec<ExcerptId>,
27304 folded: bool,
27305 },
27306 ExcerptsEdited {
27307 ids: Vec<ExcerptId>,
27308 },
27309 ExcerptsExpanded {
27310 ids: Vec<ExcerptId>,
27311 },
27312 ExpandExcerptsRequested {
27313 excerpt_ids: Vec<ExcerptId>,
27314 lines: u32,
27315 direction: ExpandExcerptDirection,
27316 },
27317 BufferEdited,
27318 Edited {
27319 transaction_id: clock::Lamport,
27320 },
27321 Reparsed(BufferId),
27322 Focused,
27323 FocusedIn,
27324 Blurred,
27325 DirtyChanged,
27326 Saved,
27327 TitleChanged,
27328 SelectionsChanged {
27329 local: bool,
27330 },
27331 ScrollPositionChanged {
27332 local: bool,
27333 autoscroll: bool,
27334 },
27335 TransactionUndone {
27336 transaction_id: clock::Lamport,
27337 },
27338 TransactionBegun {
27339 transaction_id: clock::Lamport,
27340 },
27341 CursorShapeChanged,
27342 BreadcrumbsChanged,
27343 PushedToNavHistory {
27344 anchor: Anchor,
27345 is_deactivate: bool,
27346 },
27347}
27348
27349impl EventEmitter<EditorEvent> for Editor {}
27350
27351impl Focusable for Editor {
27352 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27353 self.focus_handle.clone()
27354 }
27355}
27356
27357impl Render for Editor {
27358 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27359 EditorElement::new(&cx.entity(), self.create_style(cx))
27360 }
27361}
27362
27363impl EntityInputHandler for Editor {
27364 fn text_for_range(
27365 &mut self,
27366 range_utf16: Range<usize>,
27367 adjusted_range: &mut Option<Range<usize>>,
27368 _: &mut Window,
27369 cx: &mut Context<Self>,
27370 ) -> Option<String> {
27371 let snapshot = self.buffer.read(cx).read(cx);
27372 let start = snapshot.clip_offset_utf16(
27373 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27374 Bias::Left,
27375 );
27376 let end = snapshot.clip_offset_utf16(
27377 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27378 Bias::Right,
27379 );
27380 if (start.0.0..end.0.0) != range_utf16 {
27381 adjusted_range.replace(start.0.0..end.0.0);
27382 }
27383 Some(snapshot.text_for_range(start..end).collect())
27384 }
27385
27386 fn selected_text_range(
27387 &mut self,
27388 ignore_disabled_input: bool,
27389 _: &mut Window,
27390 cx: &mut Context<Self>,
27391 ) -> Option<UTF16Selection> {
27392 // Prevent the IME menu from appearing when holding down an alphabetic key
27393 // while input is disabled.
27394 if !ignore_disabled_input && !self.input_enabled {
27395 return None;
27396 }
27397
27398 let selection = self
27399 .selections
27400 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27401 let range = selection.range();
27402
27403 Some(UTF16Selection {
27404 range: range.start.0.0..range.end.0.0,
27405 reversed: selection.reversed,
27406 })
27407 }
27408
27409 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27410 let snapshot = self.buffer.read(cx).read(cx);
27411 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
27412 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27413 }
27414
27415 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27416 self.clear_highlights::<InputComposition>(cx);
27417 self.ime_transaction.take();
27418 }
27419
27420 fn replace_text_in_range(
27421 &mut self,
27422 range_utf16: Option<Range<usize>>,
27423 text: &str,
27424 window: &mut Window,
27425 cx: &mut Context<Self>,
27426 ) {
27427 if !self.input_enabled {
27428 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27429 return;
27430 }
27431
27432 self.transact(window, cx, |this, window, cx| {
27433 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27434 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27435 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27436 Some(this.selection_replacement_ranges(range_utf16, cx))
27437 } else {
27438 this.marked_text_ranges(cx)
27439 };
27440
27441 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27442 let newest_selection_id = this.selections.newest_anchor().id;
27443 this.selections
27444 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27445 .iter()
27446 .zip(ranges_to_replace.iter())
27447 .find_map(|(selection, range)| {
27448 if selection.id == newest_selection_id {
27449 Some(
27450 (range.start.0.0 as isize - selection.head().0.0 as isize)
27451 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27452 )
27453 } else {
27454 None
27455 }
27456 })
27457 });
27458
27459 cx.emit(EditorEvent::InputHandled {
27460 utf16_range_to_replace: range_to_replace,
27461 text: text.into(),
27462 });
27463
27464 if let Some(new_selected_ranges) = new_selected_ranges {
27465 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27466 selections.select_ranges(new_selected_ranges)
27467 });
27468 this.backspace(&Default::default(), window, cx);
27469 }
27470
27471 this.handle_input(text, window, cx);
27472 });
27473
27474 if let Some(transaction) = self.ime_transaction {
27475 self.buffer.update(cx, |buffer, cx| {
27476 buffer.group_until_transaction(transaction, cx);
27477 });
27478 }
27479
27480 self.unmark_text(window, cx);
27481 }
27482
27483 fn replace_and_mark_text_in_range(
27484 &mut self,
27485 range_utf16: Option<Range<usize>>,
27486 text: &str,
27487 new_selected_range_utf16: Option<Range<usize>>,
27488 window: &mut Window,
27489 cx: &mut Context<Self>,
27490 ) {
27491 if !self.input_enabled {
27492 return;
27493 }
27494
27495 let transaction = self.transact(window, cx, |this, window, cx| {
27496 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27497 let snapshot = this.buffer.read(cx).read(cx);
27498 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27499 for marked_range in &mut marked_ranges {
27500 marked_range.end = marked_range.start + relative_range_utf16.end;
27501 marked_range.start += relative_range_utf16.start;
27502 marked_range.start =
27503 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27504 marked_range.end =
27505 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27506 }
27507 }
27508 Some(marked_ranges)
27509 } else if let Some(range_utf16) = range_utf16 {
27510 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27511 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27512 Some(this.selection_replacement_ranges(range_utf16, cx))
27513 } else {
27514 None
27515 };
27516
27517 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27518 let newest_selection_id = this.selections.newest_anchor().id;
27519 this.selections
27520 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27521 .iter()
27522 .zip(ranges_to_replace.iter())
27523 .find_map(|(selection, range)| {
27524 if selection.id == newest_selection_id {
27525 Some(
27526 (range.start.0.0 as isize - selection.head().0.0 as isize)
27527 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27528 )
27529 } else {
27530 None
27531 }
27532 })
27533 });
27534
27535 cx.emit(EditorEvent::InputHandled {
27536 utf16_range_to_replace: range_to_replace,
27537 text: text.into(),
27538 });
27539
27540 if let Some(ranges) = ranges_to_replace {
27541 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27542 s.select_ranges(ranges)
27543 });
27544 }
27545
27546 let marked_ranges = {
27547 let snapshot = this.buffer.read(cx).read(cx);
27548 this.selections
27549 .disjoint_anchors_arc()
27550 .iter()
27551 .map(|selection| {
27552 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27553 })
27554 .collect::<Vec<_>>()
27555 };
27556
27557 if text.is_empty() {
27558 this.unmark_text(window, cx);
27559 } else {
27560 this.highlight_text::<InputComposition>(
27561 marked_ranges.clone(),
27562 HighlightStyle {
27563 underline: Some(UnderlineStyle {
27564 thickness: px(1.),
27565 color: None,
27566 wavy: false,
27567 }),
27568 ..Default::default()
27569 },
27570 cx,
27571 );
27572 }
27573
27574 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27575 let use_autoclose = this.use_autoclose;
27576 let use_auto_surround = this.use_auto_surround;
27577 this.set_use_autoclose(false);
27578 this.set_use_auto_surround(false);
27579 this.handle_input(text, window, cx);
27580 this.set_use_autoclose(use_autoclose);
27581 this.set_use_auto_surround(use_auto_surround);
27582
27583 if let Some(new_selected_range) = new_selected_range_utf16 {
27584 let snapshot = this.buffer.read(cx).read(cx);
27585 let new_selected_ranges = marked_ranges
27586 .into_iter()
27587 .map(|marked_range| {
27588 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27589 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27590 insertion_start.0 + new_selected_range.start,
27591 ));
27592 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27593 insertion_start.0 + new_selected_range.end,
27594 ));
27595 snapshot.clip_offset_utf16(new_start, Bias::Left)
27596 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27597 })
27598 .collect::<Vec<_>>();
27599
27600 drop(snapshot);
27601 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27602 selections.select_ranges(new_selected_ranges)
27603 });
27604 }
27605 });
27606
27607 self.ime_transaction = self.ime_transaction.or(transaction);
27608 if let Some(transaction) = self.ime_transaction {
27609 self.buffer.update(cx, |buffer, cx| {
27610 buffer.group_until_transaction(transaction, cx);
27611 });
27612 }
27613
27614 if self.text_highlights::<InputComposition>(cx).is_none() {
27615 self.ime_transaction.take();
27616 }
27617 }
27618
27619 fn bounds_for_range(
27620 &mut self,
27621 range_utf16: Range<usize>,
27622 element_bounds: gpui::Bounds<Pixels>,
27623 window: &mut Window,
27624 cx: &mut Context<Self>,
27625 ) -> Option<gpui::Bounds<Pixels>> {
27626 let text_layout_details = self.text_layout_details(window);
27627 let CharacterDimensions {
27628 em_width,
27629 em_advance,
27630 line_height,
27631 } = self.character_dimensions(window);
27632
27633 let snapshot = self.snapshot(window, cx);
27634 let scroll_position = snapshot.scroll_position();
27635 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
27636
27637 let start =
27638 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
27639 let x = Pixels::from(
27640 ScrollOffset::from(
27641 snapshot.x_for_display_point(start, &text_layout_details)
27642 + self.gutter_dimensions.full_width(),
27643 ) - scroll_left,
27644 );
27645 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
27646
27647 Some(Bounds {
27648 origin: element_bounds.origin + point(x, y),
27649 size: size(em_width, line_height),
27650 })
27651 }
27652
27653 fn character_index_for_point(
27654 &mut self,
27655 point: gpui::Point<Pixels>,
27656 _window: &mut Window,
27657 _cx: &mut Context<Self>,
27658 ) -> Option<usize> {
27659 let position_map = self.last_position_map.as_ref()?;
27660 if !position_map.text_hitbox.contains(&point) {
27661 return None;
27662 }
27663 let display_point = position_map.point_for_position(point).previous_valid;
27664 let anchor = position_map
27665 .snapshot
27666 .display_point_to_anchor(display_point, Bias::Left);
27667 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
27668 Some(utf16_offset.0.0)
27669 }
27670
27671 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
27672 self.input_enabled
27673 }
27674}
27675
27676trait SelectionExt {
27677 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
27678 fn spanned_rows(
27679 &self,
27680 include_end_if_at_line_start: bool,
27681 map: &DisplaySnapshot,
27682 ) -> Range<MultiBufferRow>;
27683}
27684
27685impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
27686 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
27687 let start = self
27688 .start
27689 .to_point(map.buffer_snapshot())
27690 .to_display_point(map);
27691 let end = self
27692 .end
27693 .to_point(map.buffer_snapshot())
27694 .to_display_point(map);
27695 if self.reversed {
27696 end..start
27697 } else {
27698 start..end
27699 }
27700 }
27701
27702 fn spanned_rows(
27703 &self,
27704 include_end_if_at_line_start: bool,
27705 map: &DisplaySnapshot,
27706 ) -> Range<MultiBufferRow> {
27707 let start = self.start.to_point(map.buffer_snapshot());
27708 let mut end = self.end.to_point(map.buffer_snapshot());
27709 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
27710 end.row -= 1;
27711 }
27712
27713 let buffer_start = map.prev_line_boundary(start).0;
27714 let buffer_end = map.next_line_boundary(end).0;
27715 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
27716 }
27717}
27718
27719impl<T: InvalidationRegion> InvalidationStack<T> {
27720 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
27721 where
27722 S: Clone + ToOffset,
27723 {
27724 while let Some(region) = self.last() {
27725 let all_selections_inside_invalidation_ranges =
27726 if selections.len() == region.ranges().len() {
27727 selections
27728 .iter()
27729 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
27730 .all(|(selection, invalidation_range)| {
27731 let head = selection.head().to_offset(buffer);
27732 invalidation_range.start <= head && invalidation_range.end >= head
27733 })
27734 } else {
27735 false
27736 };
27737
27738 if all_selections_inside_invalidation_ranges {
27739 break;
27740 } else {
27741 self.pop();
27742 }
27743 }
27744 }
27745}
27746
27747#[derive(Clone)]
27748struct ErasedEditorImpl(Entity<Editor>);
27749
27750impl ui_input::ErasedEditor for ErasedEditorImpl {
27751 fn text(&self, cx: &App) -> String {
27752 self.0.read(cx).text(cx)
27753 }
27754
27755 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
27756 self.0.update(cx, |this, cx| {
27757 this.set_text(text, window, cx);
27758 })
27759 }
27760
27761 fn clear(&self, window: &mut Window, cx: &mut App) {
27762 self.0.update(cx, |this, cx| this.clear(window, cx));
27763 }
27764
27765 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
27766 self.0.update(cx, |this, cx| {
27767 this.set_placeholder_text(text, window, cx);
27768 });
27769 }
27770
27771 fn focus_handle(&self, cx: &App) -> FocusHandle {
27772 self.0.read(cx).focus_handle(cx)
27773 }
27774
27775 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
27776 let settings = ThemeSettings::get_global(cx);
27777 let theme_color = cx.theme().colors();
27778
27779 let text_style = TextStyle {
27780 font_family: settings.ui_font.family.clone(),
27781 font_features: settings.ui_font.features.clone(),
27782 font_size: rems(0.875).into(),
27783 font_weight: settings.buffer_font.weight,
27784 font_style: FontStyle::Normal,
27785 line_height: relative(1.2),
27786 color: theme_color.text,
27787 ..Default::default()
27788 };
27789 let editor_style = EditorStyle {
27790 background: theme_color.ghost_element_background,
27791 local_player: cx.theme().players().local(),
27792 syntax: cx.theme().syntax().clone(),
27793 text: text_style,
27794 ..Default::default()
27795 };
27796 EditorElement::new(&self.0, editor_style).into_any()
27797 }
27798
27799 fn as_any(&self) -> &dyn Any {
27800 &self.0
27801 }
27802
27803 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
27804 self.0.update(cx, |editor, cx| {
27805 let editor_offset = editor.buffer().read(cx).len(cx);
27806 editor.change_selections(
27807 SelectionEffects::scroll(Autoscroll::Next),
27808 window,
27809 cx,
27810 |s| s.select_ranges(Some(editor_offset..editor_offset)),
27811 );
27812 });
27813 }
27814
27815 fn subscribe(
27816 &self,
27817 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
27818 window: &mut Window,
27819 cx: &mut App,
27820 ) -> Subscription {
27821 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
27822 let event = match event {
27823 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
27824 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
27825 _ => return,
27826 };
27827 (callback)(event, window, cx);
27828 })
27829 }
27830}
27831impl<T> Default for InvalidationStack<T> {
27832 fn default() -> Self {
27833 Self(Default::default())
27834 }
27835}
27836
27837impl<T> Deref for InvalidationStack<T> {
27838 type Target = Vec<T>;
27839
27840 fn deref(&self) -> &Self::Target {
27841 &self.0
27842 }
27843}
27844
27845impl<T> DerefMut for InvalidationStack<T> {
27846 fn deref_mut(&mut self) -> &mut Self::Target {
27847 &mut self.0
27848 }
27849}
27850
27851impl InvalidationRegion for SnippetState {
27852 fn ranges(&self) -> &[Range<Anchor>] {
27853 &self.ranges[self.active_index]
27854 }
27855}
27856
27857fn edit_prediction_edit_text(
27858 current_snapshot: &BufferSnapshot,
27859 edits: &[(Range<Anchor>, impl AsRef<str>)],
27860 edit_preview: &EditPreview,
27861 include_deletions: bool,
27862 cx: &App,
27863) -> HighlightedText {
27864 let edits = edits
27865 .iter()
27866 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
27867 .collect::<Vec<_>>();
27868
27869 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
27870}
27871
27872fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
27873 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
27874 // Just show the raw edit text with basic styling
27875 let mut text = String::new();
27876 let mut highlights = Vec::new();
27877
27878 let insertion_highlight_style = HighlightStyle {
27879 color: Some(cx.theme().colors().text),
27880 ..Default::default()
27881 };
27882
27883 for (_, edit_text) in edits {
27884 let start_offset = text.len();
27885 text.push_str(edit_text);
27886 let end_offset = text.len();
27887
27888 if start_offset < end_offset {
27889 highlights.push((start_offset..end_offset, insertion_highlight_style));
27890 }
27891 }
27892
27893 HighlightedText {
27894 text: text.into(),
27895 highlights,
27896 }
27897}
27898
27899pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
27900 match severity {
27901 lsp::DiagnosticSeverity::ERROR => colors.error,
27902 lsp::DiagnosticSeverity::WARNING => colors.warning,
27903 lsp::DiagnosticSeverity::INFORMATION => colors.info,
27904 lsp::DiagnosticSeverity::HINT => colors.info,
27905 _ => colors.ignored,
27906 }
27907}
27908
27909pub fn styled_runs_for_code_label<'a>(
27910 label: &'a CodeLabel,
27911 syntax_theme: &'a theme::SyntaxTheme,
27912 local_player: &'a theme::PlayerColor,
27913) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
27914 let fade_out = HighlightStyle {
27915 fade_out: Some(0.35),
27916 ..Default::default()
27917 };
27918
27919 let mut prev_end = label.filter_range.end;
27920 label
27921 .runs
27922 .iter()
27923 .enumerate()
27924 .flat_map(move |(ix, (range, highlight_id))| {
27925 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
27926 HighlightStyle {
27927 color: Some(local_player.cursor),
27928 ..Default::default()
27929 }
27930 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
27931 HighlightStyle {
27932 background_color: Some(local_player.selection),
27933 ..Default::default()
27934 }
27935 } else if let Some(style) = highlight_id.style(syntax_theme) {
27936 style
27937 } else {
27938 return Default::default();
27939 };
27940 let muted_style = style.highlight(fade_out);
27941
27942 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
27943 if range.start >= label.filter_range.end {
27944 if range.start > prev_end {
27945 runs.push((prev_end..range.start, fade_out));
27946 }
27947 runs.push((range.clone(), muted_style));
27948 } else if range.end <= label.filter_range.end {
27949 runs.push((range.clone(), style));
27950 } else {
27951 runs.push((range.start..label.filter_range.end, style));
27952 runs.push((label.filter_range.end..range.end, muted_style));
27953 }
27954 prev_end = cmp::max(prev_end, range.end);
27955
27956 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
27957 runs.push((prev_end..label.text.len(), fade_out));
27958 }
27959
27960 runs
27961 })
27962}
27963
27964pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
27965 let mut prev_index = 0;
27966 let mut prev_codepoint: Option<char> = None;
27967 text.char_indices()
27968 .chain([(text.len(), '\0')])
27969 .filter_map(move |(index, codepoint)| {
27970 let prev_codepoint = prev_codepoint.replace(codepoint)?;
27971 let is_boundary = index == text.len()
27972 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
27973 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
27974 if is_boundary {
27975 let chunk = &text[prev_index..index];
27976 prev_index = index;
27977 Some(chunk)
27978 } else {
27979 None
27980 }
27981 })
27982}
27983
27984/// Given a string of text immediately before the cursor, iterates over possible
27985/// strings a snippet could match to. More precisely: returns an iterator over
27986/// suffixes of `text` created by splitting at word boundaries (before & after
27987/// every non-word character).
27988///
27989/// Shorter suffixes are returned first.
27990pub(crate) fn snippet_candidate_suffixes(
27991 text: &str,
27992 is_word_char: impl Fn(char) -> bool,
27993) -> impl std::iter::Iterator<Item = &str> {
27994 let mut prev_index = text.len();
27995 let mut prev_codepoint = None;
27996 text.char_indices()
27997 .rev()
27998 .chain([(0, '\0')])
27999 .filter_map(move |(index, codepoint)| {
28000 let prev_index = std::mem::replace(&mut prev_index, index);
28001 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28002 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28003 None
28004 } else {
28005 let chunk = &text[prev_index..]; // go to end of string
28006 Some(chunk)
28007 }
28008 })
28009}
28010
28011pub trait RangeToAnchorExt: Sized {
28012 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28013
28014 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28015 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28016 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28017 }
28018}
28019
28020impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28021 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28022 let start_offset = self.start.to_offset(snapshot);
28023 let end_offset = self.end.to_offset(snapshot);
28024 if start_offset == end_offset {
28025 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28026 } else {
28027 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28028 }
28029 }
28030}
28031
28032pub trait RowExt {
28033 fn as_f64(&self) -> f64;
28034
28035 fn next_row(&self) -> Self;
28036
28037 fn previous_row(&self) -> Self;
28038
28039 fn minus(&self, other: Self) -> u32;
28040}
28041
28042impl RowExt for DisplayRow {
28043 fn as_f64(&self) -> f64 {
28044 self.0 as _
28045 }
28046
28047 fn next_row(&self) -> Self {
28048 Self(self.0 + 1)
28049 }
28050
28051 fn previous_row(&self) -> Self {
28052 Self(self.0.saturating_sub(1))
28053 }
28054
28055 fn minus(&self, other: Self) -> u32 {
28056 self.0 - other.0
28057 }
28058}
28059
28060impl RowExt for MultiBufferRow {
28061 fn as_f64(&self) -> f64 {
28062 self.0 as _
28063 }
28064
28065 fn next_row(&self) -> Self {
28066 Self(self.0 + 1)
28067 }
28068
28069 fn previous_row(&self) -> Self {
28070 Self(self.0.saturating_sub(1))
28071 }
28072
28073 fn minus(&self, other: Self) -> u32 {
28074 self.0 - other.0
28075 }
28076}
28077
28078trait RowRangeExt {
28079 type Row;
28080
28081 fn len(&self) -> usize;
28082
28083 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28084}
28085
28086impl RowRangeExt for Range<MultiBufferRow> {
28087 type Row = MultiBufferRow;
28088
28089 fn len(&self) -> usize {
28090 (self.end.0 - self.start.0) as usize
28091 }
28092
28093 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28094 (self.start.0..self.end.0).map(MultiBufferRow)
28095 }
28096}
28097
28098impl RowRangeExt for Range<DisplayRow> {
28099 type Row = DisplayRow;
28100
28101 fn len(&self) -> usize {
28102 (self.end.0 - self.start.0) as usize
28103 }
28104
28105 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28106 (self.start.0..self.end.0).map(DisplayRow)
28107 }
28108}
28109
28110/// If select range has more than one line, we
28111/// just point the cursor to range.start.
28112fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28113 if range.start.row == range.end.row {
28114 range
28115 } else {
28116 range.start..range.start
28117 }
28118}
28119pub struct KillRing(ClipboardItem);
28120impl Global for KillRing {}
28121
28122const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28123
28124enum BreakpointPromptEditAction {
28125 Log,
28126 Condition,
28127 HitCondition,
28128}
28129
28130struct BreakpointPromptEditor {
28131 pub(crate) prompt: Entity<Editor>,
28132 editor: WeakEntity<Editor>,
28133 breakpoint_anchor: Anchor,
28134 breakpoint: Breakpoint,
28135 edit_action: BreakpointPromptEditAction,
28136 block_ids: HashSet<CustomBlockId>,
28137 editor_margins: Arc<Mutex<EditorMargins>>,
28138 _subscriptions: Vec<Subscription>,
28139}
28140
28141impl BreakpointPromptEditor {
28142 const MAX_LINES: u8 = 4;
28143
28144 fn new(
28145 editor: WeakEntity<Editor>,
28146 breakpoint_anchor: Anchor,
28147 breakpoint: Breakpoint,
28148 edit_action: BreakpointPromptEditAction,
28149 window: &mut Window,
28150 cx: &mut Context<Self>,
28151 ) -> Self {
28152 let base_text = match edit_action {
28153 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28154 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28155 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28156 }
28157 .map(|msg| msg.to_string())
28158 .unwrap_or_default();
28159
28160 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28161 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28162
28163 let prompt = cx.new(|cx| {
28164 let mut prompt = Editor::new(
28165 EditorMode::AutoHeight {
28166 min_lines: 1,
28167 max_lines: Some(Self::MAX_LINES as usize),
28168 },
28169 buffer,
28170 None,
28171 window,
28172 cx,
28173 );
28174 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28175 prompt.set_show_cursor_when_unfocused(false, cx);
28176 prompt.set_placeholder_text(
28177 match edit_action {
28178 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28179 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28180 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28181 },
28182 window,
28183 cx,
28184 );
28185
28186 prompt
28187 });
28188
28189 Self {
28190 prompt,
28191 editor,
28192 breakpoint_anchor,
28193 breakpoint,
28194 edit_action,
28195 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28196 block_ids: Default::default(),
28197 _subscriptions: vec![],
28198 }
28199 }
28200
28201 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28202 self.block_ids.extend(block_ids)
28203 }
28204
28205 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28206 if let Some(editor) = self.editor.upgrade() {
28207 let message = self
28208 .prompt
28209 .read(cx)
28210 .buffer
28211 .read(cx)
28212 .as_singleton()
28213 .expect("A multi buffer in breakpoint prompt isn't possible")
28214 .read(cx)
28215 .as_rope()
28216 .to_string();
28217
28218 editor.update(cx, |editor, cx| {
28219 editor.edit_breakpoint_at_anchor(
28220 self.breakpoint_anchor,
28221 self.breakpoint.clone(),
28222 match self.edit_action {
28223 BreakpointPromptEditAction::Log => {
28224 BreakpointEditAction::EditLogMessage(message.into())
28225 }
28226 BreakpointPromptEditAction::Condition => {
28227 BreakpointEditAction::EditCondition(message.into())
28228 }
28229 BreakpointPromptEditAction::HitCondition => {
28230 BreakpointEditAction::EditHitCondition(message.into())
28231 }
28232 },
28233 cx,
28234 );
28235
28236 editor.remove_blocks(self.block_ids.clone(), None, cx);
28237 cx.focus_self(window);
28238 });
28239 }
28240 }
28241
28242 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28243 self.editor
28244 .update(cx, |editor, cx| {
28245 editor.remove_blocks(self.block_ids.clone(), None, cx);
28246 window.focus(&editor.focus_handle, cx);
28247 })
28248 .log_err();
28249 }
28250
28251 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28252 let settings = ThemeSettings::get_global(cx);
28253 let text_style = TextStyle {
28254 color: if self.prompt.read(cx).read_only(cx) {
28255 cx.theme().colors().text_disabled
28256 } else {
28257 cx.theme().colors().text
28258 },
28259 font_family: settings.buffer_font.family.clone(),
28260 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28261 font_size: settings.buffer_font_size(cx).into(),
28262 font_weight: settings.buffer_font.weight,
28263 line_height: relative(settings.buffer_line_height.value()),
28264 ..Default::default()
28265 };
28266 EditorElement::new(
28267 &self.prompt,
28268 EditorStyle {
28269 background: cx.theme().colors().editor_background,
28270 local_player: cx.theme().players().local(),
28271 text: text_style,
28272 ..Default::default()
28273 },
28274 )
28275 }
28276}
28277
28278impl Render for BreakpointPromptEditor {
28279 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28280 let editor_margins = *self.editor_margins.lock();
28281 let gutter_dimensions = editor_margins.gutter;
28282 h_flex()
28283 .key_context("Editor")
28284 .bg(cx.theme().colors().editor_background)
28285 .border_y_1()
28286 .border_color(cx.theme().status().info_border)
28287 .size_full()
28288 .py(window.line_height() / 2.5)
28289 .on_action(cx.listener(Self::confirm))
28290 .on_action(cx.listener(Self::cancel))
28291 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28292 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28293 }
28294}
28295
28296impl Focusable for BreakpointPromptEditor {
28297 fn focus_handle(&self, cx: &App) -> FocusHandle {
28298 self.prompt.focus_handle(cx)
28299 }
28300}
28301
28302fn all_edits_insertions_or_deletions(
28303 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28304 snapshot: &MultiBufferSnapshot,
28305) -> bool {
28306 let mut all_insertions = true;
28307 let mut all_deletions = true;
28308
28309 for (range, new_text) in edits.iter() {
28310 let range_is_empty = range.to_offset(snapshot).is_empty();
28311 let text_is_empty = new_text.is_empty();
28312
28313 if range_is_empty != text_is_empty {
28314 if range_is_empty {
28315 all_deletions = false;
28316 } else {
28317 all_insertions = false;
28318 }
28319 } else {
28320 return false;
28321 }
28322
28323 if !all_insertions && !all_deletions {
28324 return false;
28325 }
28326 }
28327 all_insertions || all_deletions
28328}
28329
28330struct MissingEditPredictionKeybindingTooltip;
28331
28332impl Render for MissingEditPredictionKeybindingTooltip {
28333 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28334 ui::tooltip_container(cx, |container, cx| {
28335 container
28336 .flex_shrink_0()
28337 .max_w_80()
28338 .min_h(rems_from_px(124.))
28339 .justify_between()
28340 .child(
28341 v_flex()
28342 .flex_1()
28343 .text_ui_sm(cx)
28344 .child(Label::new("Conflict with Accept Keybinding"))
28345 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28346 )
28347 .child(
28348 h_flex()
28349 .pb_1()
28350 .gap_1()
28351 .items_end()
28352 .w_full()
28353 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28354 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28355 }))
28356 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28357 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28358 })),
28359 )
28360 })
28361 }
28362}
28363
28364#[derive(Debug, Clone, Copy, PartialEq)]
28365pub struct LineHighlight {
28366 pub background: Background,
28367 pub border: Option<gpui::Hsla>,
28368 pub include_gutter: bool,
28369 pub type_id: Option<TypeId>,
28370}
28371
28372struct LineManipulationResult {
28373 pub new_text: String,
28374 pub line_count_before: usize,
28375 pub line_count_after: usize,
28376}
28377
28378fn render_diff_hunk_controls(
28379 row: u32,
28380 status: &DiffHunkStatus,
28381 hunk_range: Range<Anchor>,
28382 is_created_file: bool,
28383 line_height: Pixels,
28384 editor: &Entity<Editor>,
28385 _window: &mut Window,
28386 cx: &mut App,
28387) -> AnyElement {
28388 h_flex()
28389 .h(line_height)
28390 .mr_1()
28391 .gap_1()
28392 .px_0p5()
28393 .pb_1()
28394 .border_x_1()
28395 .border_b_1()
28396 .border_color(cx.theme().colors().border_variant)
28397 .rounded_b_lg()
28398 .bg(cx.theme().colors().editor_background)
28399 .gap_1()
28400 .block_mouse_except_scroll()
28401 .shadow_md()
28402 .child(if status.has_secondary_hunk() {
28403 Button::new(("stage", row as u64), "Stage")
28404 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28405 .tooltip({
28406 let focus_handle = editor.focus_handle(cx);
28407 move |_window, cx| {
28408 Tooltip::for_action_in(
28409 "Stage Hunk",
28410 &::git::ToggleStaged,
28411 &focus_handle,
28412 cx,
28413 )
28414 }
28415 })
28416 .on_click({
28417 let editor = editor.clone();
28418 move |_event, _window, cx| {
28419 editor.update(cx, |editor, cx| {
28420 editor.stage_or_unstage_diff_hunks(
28421 true,
28422 vec![hunk_range.start..hunk_range.start],
28423 cx,
28424 );
28425 });
28426 }
28427 })
28428 } else {
28429 Button::new(("unstage", row as u64), "Unstage")
28430 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28431 .tooltip({
28432 let focus_handle = editor.focus_handle(cx);
28433 move |_window, cx| {
28434 Tooltip::for_action_in(
28435 "Unstage Hunk",
28436 &::git::ToggleStaged,
28437 &focus_handle,
28438 cx,
28439 )
28440 }
28441 })
28442 .on_click({
28443 let editor = editor.clone();
28444 move |_event, _window, cx| {
28445 editor.update(cx, |editor, cx| {
28446 editor.stage_or_unstage_diff_hunks(
28447 false,
28448 vec![hunk_range.start..hunk_range.start],
28449 cx,
28450 );
28451 });
28452 }
28453 })
28454 })
28455 .child(
28456 Button::new(("restore", row as u64), "Restore")
28457 .tooltip({
28458 let focus_handle = editor.focus_handle(cx);
28459 move |_window, cx| {
28460 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28461 }
28462 })
28463 .on_click({
28464 let editor = editor.clone();
28465 move |_event, window, cx| {
28466 editor.update(cx, |editor, cx| {
28467 let snapshot = editor.snapshot(window, cx);
28468 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28469 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28470 });
28471 }
28472 })
28473 .disabled(is_created_file),
28474 )
28475 .when(
28476 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28477 |el| {
28478 el.child(
28479 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28480 .shape(IconButtonShape::Square)
28481 .icon_size(IconSize::Small)
28482 // .disabled(!has_multiple_hunks)
28483 .tooltip({
28484 let focus_handle = editor.focus_handle(cx);
28485 move |_window, cx| {
28486 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28487 }
28488 })
28489 .on_click({
28490 let editor = editor.clone();
28491 move |_event, window, cx| {
28492 editor.update(cx, |editor, cx| {
28493 let snapshot = editor.snapshot(window, cx);
28494 let position =
28495 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28496 editor.go_to_hunk_before_or_after_position(
28497 &snapshot,
28498 position,
28499 Direction::Next,
28500 window,
28501 cx,
28502 );
28503 editor.expand_selected_diff_hunks(cx);
28504 });
28505 }
28506 }),
28507 )
28508 .child(
28509 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28510 .shape(IconButtonShape::Square)
28511 .icon_size(IconSize::Small)
28512 // .disabled(!has_multiple_hunks)
28513 .tooltip({
28514 let focus_handle = editor.focus_handle(cx);
28515 move |_window, cx| {
28516 Tooltip::for_action_in(
28517 "Previous Hunk",
28518 &GoToPreviousHunk,
28519 &focus_handle,
28520 cx,
28521 )
28522 }
28523 })
28524 .on_click({
28525 let editor = editor.clone();
28526 move |_event, window, cx| {
28527 editor.update(cx, |editor, cx| {
28528 let snapshot = editor.snapshot(window, cx);
28529 let point =
28530 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28531 editor.go_to_hunk_before_or_after_position(
28532 &snapshot,
28533 point,
28534 Direction::Prev,
28535 window,
28536 cx,
28537 );
28538 editor.expand_selected_diff_hunks(cx);
28539 });
28540 }
28541 }),
28542 )
28543 },
28544 )
28545 .into_any_element()
28546}
28547
28548pub fn multibuffer_context_lines(cx: &App) -> u32 {
28549 EditorSettings::try_get(cx)
28550 .map(|settings| settings.excerpt_context_lines)
28551 .unwrap_or(2)
28552 .min(32)
28553}