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::{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 move_cursors_to_syntax_nodes(
16561 &mut self,
16562 window: &mut Window,
16563 cx: &mut Context<Self>,
16564 move_to_end: bool,
16565 ) -> bool {
16566 let old_selections: Box<[_]> = self
16567 .selections
16568 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16569 .into();
16570 if old_selections.is_empty() {
16571 return false;
16572 }
16573
16574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16575
16576 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16577 let buffer = self.buffer.read(cx).snapshot(cx);
16578
16579 let mut any_cursor_moved = false;
16580 let new_selections = old_selections
16581 .iter()
16582 .map(|selection| {
16583 if !selection.is_empty() {
16584 return selection.clone();
16585 }
16586
16587 let selection_pos = selection.head();
16588 let old_range = selection_pos..selection_pos;
16589
16590 let mut new_pos = selection_pos;
16591 let mut search_range = old_range;
16592 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16593 search_range = range.clone();
16594 if !node.is_named()
16595 || display_map.intersects_fold(range.start)
16596 || display_map.intersects_fold(range.end)
16597 // If cursor is already at the end of the syntax node, continue searching
16598 || (move_to_end && range.end == selection_pos)
16599 // If cursor is already at the start of the syntax node, continue searching
16600 || (!move_to_end && range.start == selection_pos)
16601 {
16602 continue;
16603 }
16604
16605 // If we found a string_content node, find the largest parent that is still string_content
16606 // Enables us to skip to the end of strings without taking multiple steps inside the string
16607 let (_, final_range) = if node.kind() == "string_content" {
16608 let mut current_node = node;
16609 let mut current_range = range;
16610 while let Some((parent, parent_range)) =
16611 buffer.syntax_ancestor(current_range.clone())
16612 {
16613 if parent.kind() == "string_content" {
16614 current_node = parent;
16615 current_range = parent_range;
16616 } else {
16617 break;
16618 }
16619 }
16620
16621 (current_node, current_range)
16622 } else {
16623 (node, range)
16624 };
16625
16626 new_pos = if move_to_end {
16627 final_range.end
16628 } else {
16629 final_range.start
16630 };
16631
16632 break;
16633 }
16634
16635 any_cursor_moved |= new_pos != selection_pos;
16636
16637 Selection {
16638 id: selection.id,
16639 start: new_pos,
16640 end: new_pos,
16641 goal: SelectionGoal::None,
16642 reversed: false,
16643 }
16644 })
16645 .collect::<Vec<_>>();
16646
16647 self.change_selections(Default::default(), window, cx, |s| {
16648 s.select(new_selections);
16649 });
16650 self.request_autoscroll(Autoscroll::newest(), cx);
16651
16652 any_cursor_moved
16653 }
16654
16655 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16656 if !EditorSettings::get_global(cx).gutter.runnables {
16657 self.clear_tasks();
16658 return Task::ready(());
16659 }
16660 let project = self.project().map(Entity::downgrade);
16661 let task_sources = self.lsp_task_sources(cx);
16662 let multi_buffer = self.buffer.downgrade();
16663 cx.spawn_in(window, async move |editor, cx| {
16664 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16665 let Some(project) = project.and_then(|p| p.upgrade()) else {
16666 return;
16667 };
16668 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16669 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16670 }) else {
16671 return;
16672 };
16673
16674 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16675 if hide_runnables {
16676 return;
16677 }
16678 let new_rows =
16679 cx.background_spawn({
16680 let snapshot = display_snapshot.clone();
16681 async move {
16682 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16683 }
16684 })
16685 .await;
16686 let Ok(lsp_tasks) =
16687 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16688 else {
16689 return;
16690 };
16691 let lsp_tasks = lsp_tasks.await;
16692
16693 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16694 lsp_tasks
16695 .into_iter()
16696 .flat_map(|(kind, tasks)| {
16697 tasks.into_iter().filter_map(move |(location, task)| {
16698 Some((kind.clone(), location?, task))
16699 })
16700 })
16701 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16702 let buffer = location.target.buffer;
16703 let buffer_snapshot = buffer.read(cx).snapshot();
16704 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16705 |(excerpt_id, snapshot, _)| {
16706 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16707 display_snapshot
16708 .buffer_snapshot()
16709 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16710 } else {
16711 None
16712 }
16713 },
16714 );
16715 if let Some(offset) = offset {
16716 let task_buffer_range =
16717 location.target.range.to_point(&buffer_snapshot);
16718 let context_buffer_range =
16719 task_buffer_range.to_offset(&buffer_snapshot);
16720 let context_range = BufferOffset(context_buffer_range.start)
16721 ..BufferOffset(context_buffer_range.end);
16722
16723 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16724 .or_insert_with(|| RunnableTasks {
16725 templates: Vec::new(),
16726 offset,
16727 column: task_buffer_range.start.column,
16728 extra_variables: HashMap::default(),
16729 context_range,
16730 })
16731 .templates
16732 .push((kind, task.original_task().clone()));
16733 }
16734
16735 acc
16736 })
16737 }) else {
16738 return;
16739 };
16740
16741 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16742 buffer.language_settings(cx).tasks.prefer_lsp
16743 }) else {
16744 return;
16745 };
16746
16747 let rows = Self::runnable_rows(
16748 project,
16749 display_snapshot,
16750 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16751 new_rows,
16752 cx.clone(),
16753 )
16754 .await;
16755 editor
16756 .update(cx, |editor, _| {
16757 editor.clear_tasks();
16758 for (key, mut value) in rows {
16759 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16760 value.templates.extend(lsp_tasks.templates);
16761 }
16762
16763 editor.insert_tasks(key, value);
16764 }
16765 for (key, value) in lsp_tasks_by_rows {
16766 editor.insert_tasks(key, value);
16767 }
16768 })
16769 .ok();
16770 })
16771 }
16772 fn fetch_runnable_ranges(
16773 snapshot: &DisplaySnapshot,
16774 range: Range<Anchor>,
16775 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16776 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16777 }
16778
16779 fn runnable_rows(
16780 project: Entity<Project>,
16781 snapshot: DisplaySnapshot,
16782 prefer_lsp: bool,
16783 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16784 cx: AsyncWindowContext,
16785 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16786 cx.spawn(async move |cx| {
16787 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16788 for (run_range, mut runnable) in runnable_ranges {
16789 let Some(tasks) = cx
16790 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16791 .ok()
16792 else {
16793 continue;
16794 };
16795 let mut tasks = tasks.await;
16796
16797 if prefer_lsp {
16798 tasks.retain(|(task_kind, _)| {
16799 !matches!(task_kind, TaskSourceKind::Language { .. })
16800 });
16801 }
16802 if tasks.is_empty() {
16803 continue;
16804 }
16805
16806 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16807 let Some(row) = snapshot
16808 .buffer_snapshot()
16809 .buffer_line_for_row(MultiBufferRow(point.row))
16810 .map(|(_, range)| range.start.row)
16811 else {
16812 continue;
16813 };
16814
16815 let context_range =
16816 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16817 runnable_rows.push((
16818 (runnable.buffer_id, row),
16819 RunnableTasks {
16820 templates: tasks,
16821 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16822 context_range,
16823 column: point.column,
16824 extra_variables: runnable.extra_captures,
16825 },
16826 ));
16827 }
16828 runnable_rows
16829 })
16830 }
16831
16832 fn templates_with_tags(
16833 project: &Entity<Project>,
16834 runnable: &mut Runnable,
16835 cx: &mut App,
16836 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16837 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16838 let (worktree_id, file) = project
16839 .buffer_for_id(runnable.buffer, cx)
16840 .and_then(|buffer| buffer.read(cx).file())
16841 .map(|file| (file.worktree_id(cx), file.clone()))
16842 .unzip();
16843
16844 (
16845 project.task_store().read(cx).task_inventory().cloned(),
16846 worktree_id,
16847 file,
16848 )
16849 });
16850
16851 let tags = mem::take(&mut runnable.tags);
16852 let language = runnable.language.clone();
16853 cx.spawn(async move |cx| {
16854 let mut templates_with_tags = Vec::new();
16855 if let Some(inventory) = inventory {
16856 for RunnableTag(tag) in tags {
16857 let new_tasks = inventory.update(cx, |inventory, cx| {
16858 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16859 });
16860 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16861 move |(_, template)| {
16862 template.tags.iter().any(|source_tag| source_tag == &tag)
16863 },
16864 ));
16865 }
16866 }
16867 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16868
16869 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16870 // Strongest source wins; if we have worktree tag binding, prefer that to
16871 // global and language bindings;
16872 // if we have a global binding, prefer that to language binding.
16873 let first_mismatch = templates_with_tags
16874 .iter()
16875 .position(|(tag_source, _)| tag_source != leading_tag_source);
16876 if let Some(index) = first_mismatch {
16877 templates_with_tags.truncate(index);
16878 }
16879 }
16880
16881 templates_with_tags
16882 })
16883 }
16884
16885 pub fn move_to_enclosing_bracket(
16886 &mut self,
16887 _: &MoveToEnclosingBracket,
16888 window: &mut Window,
16889 cx: &mut Context<Self>,
16890 ) {
16891 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16892 self.change_selections(Default::default(), window, cx, |s| {
16893 s.move_offsets_with(|snapshot, selection| {
16894 let Some(enclosing_bracket_ranges) =
16895 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16896 else {
16897 return;
16898 };
16899
16900 let mut best_length = usize::MAX;
16901 let mut best_inside = false;
16902 let mut best_in_bracket_range = false;
16903 let mut best_destination = None;
16904 for (open, close) in enclosing_bracket_ranges {
16905 let close = close.to_inclusive();
16906 let length = *close.end() - open.start;
16907 let inside = selection.start >= open.end && selection.end <= *close.start();
16908 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16909 || close.contains(&selection.head());
16910
16911 // If best is next to a bracket and current isn't, skip
16912 if !in_bracket_range && best_in_bracket_range {
16913 continue;
16914 }
16915
16916 // Prefer smaller lengths unless best is inside and current isn't
16917 if length > best_length && (best_inside || !inside) {
16918 continue;
16919 }
16920
16921 best_length = length;
16922 best_inside = inside;
16923 best_in_bracket_range = in_bracket_range;
16924 best_destination = Some(
16925 if close.contains(&selection.start) && close.contains(&selection.end) {
16926 if inside { open.end } else { open.start }
16927 } else if inside {
16928 *close.start()
16929 } else {
16930 *close.end()
16931 },
16932 );
16933 }
16934
16935 if let Some(destination) = best_destination {
16936 selection.collapse_to(destination, SelectionGoal::None);
16937 }
16938 })
16939 });
16940 }
16941
16942 pub fn undo_selection(
16943 &mut self,
16944 _: &UndoSelection,
16945 window: &mut Window,
16946 cx: &mut Context<Self>,
16947 ) {
16948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16949 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16950 self.selection_history.mode = SelectionHistoryMode::Undoing;
16951 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16952 this.end_selection(window, cx);
16953 this.change_selections(
16954 SelectionEffects::scroll(Autoscroll::newest()),
16955 window,
16956 cx,
16957 |s| s.select_anchors(entry.selections.to_vec()),
16958 );
16959 });
16960 self.selection_history.mode = SelectionHistoryMode::Normal;
16961
16962 self.select_next_state = entry.select_next_state;
16963 self.select_prev_state = entry.select_prev_state;
16964 self.add_selections_state = entry.add_selections_state;
16965 }
16966 }
16967
16968 pub fn redo_selection(
16969 &mut self,
16970 _: &RedoSelection,
16971 window: &mut Window,
16972 cx: &mut Context<Self>,
16973 ) {
16974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16975 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16976 self.selection_history.mode = SelectionHistoryMode::Redoing;
16977 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16978 this.end_selection(window, cx);
16979 this.change_selections(
16980 SelectionEffects::scroll(Autoscroll::newest()),
16981 window,
16982 cx,
16983 |s| s.select_anchors(entry.selections.to_vec()),
16984 );
16985 });
16986 self.selection_history.mode = SelectionHistoryMode::Normal;
16987
16988 self.select_next_state = entry.select_next_state;
16989 self.select_prev_state = entry.select_prev_state;
16990 self.add_selections_state = entry.add_selections_state;
16991 }
16992 }
16993
16994 pub fn expand_excerpts(
16995 &mut self,
16996 action: &ExpandExcerpts,
16997 _: &mut Window,
16998 cx: &mut Context<Self>,
16999 ) {
17000 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17001 }
17002
17003 pub fn expand_excerpts_down(
17004 &mut self,
17005 action: &ExpandExcerptsDown,
17006 _: &mut Window,
17007 cx: &mut Context<Self>,
17008 ) {
17009 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17010 }
17011
17012 pub fn expand_excerpts_up(
17013 &mut self,
17014 action: &ExpandExcerptsUp,
17015 _: &mut Window,
17016 cx: &mut Context<Self>,
17017 ) {
17018 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17019 }
17020
17021 pub fn expand_excerpts_for_direction(
17022 &mut self,
17023 lines: u32,
17024 direction: ExpandExcerptDirection,
17025 cx: &mut Context<Self>,
17026 ) {
17027 let selections = self.selections.disjoint_anchors_arc();
17028
17029 let lines = if lines == 0 {
17030 EditorSettings::get_global(cx).expand_excerpt_lines
17031 } else {
17032 lines
17033 };
17034
17035 let snapshot = self.buffer.read(cx).snapshot(cx);
17036 let mut excerpt_ids = selections
17037 .iter()
17038 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17039 .collect::<Vec<_>>();
17040 excerpt_ids.sort();
17041 excerpt_ids.dedup();
17042
17043 if self.delegate_expand_excerpts {
17044 cx.emit(EditorEvent::ExpandExcerptsRequested {
17045 excerpt_ids,
17046 lines,
17047 direction,
17048 });
17049 return;
17050 }
17051
17052 self.buffer.update(cx, |buffer, cx| {
17053 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17054 })
17055 }
17056
17057 pub fn expand_excerpt(
17058 &mut self,
17059 excerpt: ExcerptId,
17060 direction: ExpandExcerptDirection,
17061 window: &mut Window,
17062 cx: &mut Context<Self>,
17063 ) {
17064 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17065
17066 if self.delegate_expand_excerpts {
17067 cx.emit(EditorEvent::ExpandExcerptsRequested {
17068 excerpt_ids: vec![excerpt],
17069 lines: lines_to_expand,
17070 direction,
17071 });
17072 return;
17073 }
17074
17075 let current_scroll_position = self.scroll_position(cx);
17076 let mut scroll = None;
17077
17078 if direction == ExpandExcerptDirection::Down {
17079 let multi_buffer = self.buffer.read(cx);
17080 let snapshot = multi_buffer.snapshot(cx);
17081 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17082 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17083 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17084 {
17085 let buffer_snapshot = buffer.read(cx).snapshot();
17086 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17087 let last_row = buffer_snapshot.max_point().row;
17088 let lines_below = last_row.saturating_sub(excerpt_end_row);
17089 if lines_below >= lines_to_expand {
17090 scroll = Some(
17091 current_scroll_position
17092 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17093 );
17094 }
17095 }
17096 }
17097 if direction == ExpandExcerptDirection::Up
17098 && self
17099 .buffer
17100 .read(cx)
17101 .snapshot(cx)
17102 .excerpt_before(excerpt)
17103 .is_none()
17104 {
17105 scroll = Some(current_scroll_position);
17106 }
17107
17108 self.buffer.update(cx, |buffer, cx| {
17109 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17110 });
17111
17112 if let Some(new_scroll_position) = scroll {
17113 self.set_scroll_position(new_scroll_position, window, cx);
17114 }
17115 }
17116
17117 pub fn go_to_singleton_buffer_point(
17118 &mut self,
17119 point: Point,
17120 window: &mut Window,
17121 cx: &mut Context<Self>,
17122 ) {
17123 self.go_to_singleton_buffer_range(point..point, window, cx);
17124 }
17125
17126 pub fn go_to_singleton_buffer_range(
17127 &mut self,
17128 range: Range<Point>,
17129 window: &mut Window,
17130 cx: &mut Context<Self>,
17131 ) {
17132 let multibuffer = self.buffer().read(cx);
17133 let Some(buffer) = multibuffer.as_singleton() else {
17134 return;
17135 };
17136 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17137 return;
17138 };
17139 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17140 return;
17141 };
17142 self.change_selections(
17143 SelectionEffects::default().nav_history(true),
17144 window,
17145 cx,
17146 |s| s.select_anchor_ranges([start..end]),
17147 );
17148 }
17149
17150 pub fn go_to_diagnostic(
17151 &mut self,
17152 action: &GoToDiagnostic,
17153 window: &mut Window,
17154 cx: &mut Context<Self>,
17155 ) {
17156 if !self.diagnostics_enabled() {
17157 return;
17158 }
17159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17160 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17161 }
17162
17163 pub fn go_to_prev_diagnostic(
17164 &mut self,
17165 action: &GoToPreviousDiagnostic,
17166 window: &mut Window,
17167 cx: &mut Context<Self>,
17168 ) {
17169 if !self.diagnostics_enabled() {
17170 return;
17171 }
17172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17173 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17174 }
17175
17176 pub fn go_to_diagnostic_impl(
17177 &mut self,
17178 direction: Direction,
17179 severity: GoToDiagnosticSeverityFilter,
17180 window: &mut Window,
17181 cx: &mut Context<Self>,
17182 ) {
17183 let buffer = self.buffer.read(cx).snapshot(cx);
17184 let selection = self
17185 .selections
17186 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17187
17188 let mut active_group_id = None;
17189 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17190 && active_group.active_range.start.to_offset(&buffer) == selection.start
17191 {
17192 active_group_id = Some(active_group.group_id);
17193 }
17194
17195 fn filtered<'a>(
17196 severity: GoToDiagnosticSeverityFilter,
17197 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17198 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17199 diagnostics
17200 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17201 .filter(|entry| entry.range.start != entry.range.end)
17202 .filter(|entry| !entry.diagnostic.is_unnecessary)
17203 }
17204
17205 let before = filtered(
17206 severity,
17207 buffer
17208 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17209 .filter(|entry| entry.range.start <= selection.start),
17210 );
17211 let after = filtered(
17212 severity,
17213 buffer
17214 .diagnostics_in_range(selection.start..buffer.len())
17215 .filter(|entry| entry.range.start >= selection.start),
17216 );
17217
17218 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17219 if direction == Direction::Prev {
17220 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17221 {
17222 for diagnostic in prev_diagnostics.into_iter().rev() {
17223 if diagnostic.range.start != selection.start
17224 || active_group_id
17225 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17226 {
17227 found = Some(diagnostic);
17228 break 'outer;
17229 }
17230 }
17231 }
17232 } else {
17233 for diagnostic in after.chain(before) {
17234 if diagnostic.range.start != selection.start
17235 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17236 {
17237 found = Some(diagnostic);
17238 break;
17239 }
17240 }
17241 }
17242 let Some(next_diagnostic) = found else {
17243 return;
17244 };
17245
17246 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17247 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17248 return;
17249 };
17250 let snapshot = self.snapshot(window, cx);
17251 if snapshot.intersects_fold(next_diagnostic.range.start) {
17252 self.unfold_ranges(
17253 std::slice::from_ref(&next_diagnostic.range),
17254 true,
17255 false,
17256 cx,
17257 );
17258 }
17259 self.change_selections(Default::default(), window, cx, |s| {
17260 s.select_ranges(vec![
17261 next_diagnostic.range.start..next_diagnostic.range.start,
17262 ])
17263 });
17264 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17265 self.refresh_edit_prediction(false, true, window, cx);
17266 }
17267
17268 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17270 let snapshot = self.snapshot(window, cx);
17271 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17272 self.go_to_hunk_before_or_after_position(
17273 &snapshot,
17274 selection.head(),
17275 Direction::Next,
17276 window,
17277 cx,
17278 );
17279 }
17280
17281 pub fn go_to_hunk_before_or_after_position(
17282 &mut self,
17283 snapshot: &EditorSnapshot,
17284 position: Point,
17285 direction: Direction,
17286 window: &mut Window,
17287 cx: &mut Context<Editor>,
17288 ) {
17289 let row = if direction == Direction::Next {
17290 self.hunk_after_position(snapshot, position)
17291 .map(|hunk| hunk.row_range.start)
17292 } else {
17293 self.hunk_before_position(snapshot, position)
17294 };
17295
17296 if let Some(row) = row {
17297 let destination = Point::new(row.0, 0);
17298 let autoscroll = Autoscroll::center();
17299
17300 self.unfold_ranges(&[destination..destination], false, false, cx);
17301 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17302 s.select_ranges([destination..destination]);
17303 });
17304 }
17305 }
17306
17307 fn hunk_after_position(
17308 &mut self,
17309 snapshot: &EditorSnapshot,
17310 position: Point,
17311 ) -> Option<MultiBufferDiffHunk> {
17312 snapshot
17313 .buffer_snapshot()
17314 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17315 .find(|hunk| hunk.row_range.start.0 > position.row)
17316 .or_else(|| {
17317 snapshot
17318 .buffer_snapshot()
17319 .diff_hunks_in_range(Point::zero()..position)
17320 .find(|hunk| hunk.row_range.end.0 < position.row)
17321 })
17322 }
17323
17324 fn go_to_prev_hunk(
17325 &mut self,
17326 _: &GoToPreviousHunk,
17327 window: &mut Window,
17328 cx: &mut Context<Self>,
17329 ) {
17330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17331 let snapshot = self.snapshot(window, cx);
17332 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17333 self.go_to_hunk_before_or_after_position(
17334 &snapshot,
17335 selection.head(),
17336 Direction::Prev,
17337 window,
17338 cx,
17339 );
17340 }
17341
17342 fn hunk_before_position(
17343 &mut self,
17344 snapshot: &EditorSnapshot,
17345 position: Point,
17346 ) -> Option<MultiBufferRow> {
17347 snapshot
17348 .buffer_snapshot()
17349 .diff_hunk_before(position)
17350 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17351 }
17352
17353 fn go_to_next_change(
17354 &mut self,
17355 _: &GoToNextChange,
17356 window: &mut Window,
17357 cx: &mut Context<Self>,
17358 ) {
17359 if let Some(selections) = self
17360 .change_list
17361 .next_change(1, Direction::Next)
17362 .map(|s| s.to_vec())
17363 {
17364 self.change_selections(Default::default(), window, cx, |s| {
17365 let map = s.display_snapshot();
17366 s.select_display_ranges(selections.iter().map(|a| {
17367 let point = a.to_display_point(&map);
17368 point..point
17369 }))
17370 })
17371 }
17372 }
17373
17374 fn go_to_previous_change(
17375 &mut self,
17376 _: &GoToPreviousChange,
17377 window: &mut Window,
17378 cx: &mut Context<Self>,
17379 ) {
17380 if let Some(selections) = self
17381 .change_list
17382 .next_change(1, Direction::Prev)
17383 .map(|s| s.to_vec())
17384 {
17385 self.change_selections(Default::default(), window, cx, |s| {
17386 let map = s.display_snapshot();
17387 s.select_display_ranges(selections.iter().map(|a| {
17388 let point = a.to_display_point(&map);
17389 point..point
17390 }))
17391 })
17392 }
17393 }
17394
17395 pub fn go_to_next_document_highlight(
17396 &mut self,
17397 _: &GoToNextDocumentHighlight,
17398 window: &mut Window,
17399 cx: &mut Context<Self>,
17400 ) {
17401 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17402 }
17403
17404 pub fn go_to_prev_document_highlight(
17405 &mut self,
17406 _: &GoToPreviousDocumentHighlight,
17407 window: &mut Window,
17408 cx: &mut Context<Self>,
17409 ) {
17410 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17411 }
17412
17413 pub fn go_to_document_highlight_before_or_after_position(
17414 &mut self,
17415 direction: Direction,
17416 window: &mut Window,
17417 cx: &mut Context<Editor>,
17418 ) {
17419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17420 let snapshot = self.snapshot(window, cx);
17421 let buffer = &snapshot.buffer_snapshot();
17422 let position = self
17423 .selections
17424 .newest::<Point>(&snapshot.display_snapshot)
17425 .head();
17426 let anchor_position = buffer.anchor_after(position);
17427
17428 // Get all document highlights (both read and write)
17429 let mut all_highlights = Vec::new();
17430
17431 if let Some((_, read_highlights)) = self
17432 .background_highlights
17433 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17434 {
17435 all_highlights.extend(read_highlights.iter());
17436 }
17437
17438 if let Some((_, write_highlights)) = self
17439 .background_highlights
17440 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17441 {
17442 all_highlights.extend(write_highlights.iter());
17443 }
17444
17445 if all_highlights.is_empty() {
17446 return;
17447 }
17448
17449 // Sort highlights by position
17450 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17451
17452 let target_highlight = match direction {
17453 Direction::Next => {
17454 // Find the first highlight after the current position
17455 all_highlights
17456 .iter()
17457 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17458 }
17459 Direction::Prev => {
17460 // Find the last highlight before the current position
17461 all_highlights
17462 .iter()
17463 .rev()
17464 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17465 }
17466 };
17467
17468 if let Some(highlight) = target_highlight {
17469 let destination = highlight.start.to_point(buffer);
17470 let autoscroll = Autoscroll::center();
17471
17472 self.unfold_ranges(&[destination..destination], false, false, cx);
17473 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17474 s.select_ranges([destination..destination]);
17475 });
17476 }
17477 }
17478
17479 fn go_to_line<T: 'static>(
17480 &mut self,
17481 position: Anchor,
17482 highlight_color: Option<Hsla>,
17483 window: &mut Window,
17484 cx: &mut Context<Self>,
17485 ) {
17486 let snapshot = self.snapshot(window, cx).display_snapshot;
17487 let position = position.to_point(&snapshot.buffer_snapshot());
17488 let start = snapshot
17489 .buffer_snapshot()
17490 .clip_point(Point::new(position.row, 0), Bias::Left);
17491 let end = start + Point::new(1, 0);
17492 let start = snapshot.buffer_snapshot().anchor_before(start);
17493 let end = snapshot.buffer_snapshot().anchor_before(end);
17494
17495 self.highlight_rows::<T>(
17496 start..end,
17497 highlight_color
17498 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17499 Default::default(),
17500 cx,
17501 );
17502
17503 if self.buffer.read(cx).is_singleton() {
17504 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17505 }
17506 }
17507
17508 pub fn go_to_definition(
17509 &mut self,
17510 _: &GoToDefinition,
17511 window: &mut Window,
17512 cx: &mut Context<Self>,
17513 ) -> Task<Result<Navigated>> {
17514 let definition =
17515 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17516 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17517 cx.spawn_in(window, async move |editor, cx| {
17518 if definition.await? == Navigated::Yes {
17519 return Ok(Navigated::Yes);
17520 }
17521 match fallback_strategy {
17522 GoToDefinitionFallback::None => Ok(Navigated::No),
17523 GoToDefinitionFallback::FindAllReferences => {
17524 match editor.update_in(cx, |editor, window, cx| {
17525 editor.find_all_references(&FindAllReferences::default(), window, cx)
17526 })? {
17527 Some(references) => references.await,
17528 None => Ok(Navigated::No),
17529 }
17530 }
17531 }
17532 })
17533 }
17534
17535 pub fn go_to_declaration(
17536 &mut self,
17537 _: &GoToDeclaration,
17538 window: &mut Window,
17539 cx: &mut Context<Self>,
17540 ) -> Task<Result<Navigated>> {
17541 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17542 }
17543
17544 pub fn go_to_declaration_split(
17545 &mut self,
17546 _: &GoToDeclaration,
17547 window: &mut Window,
17548 cx: &mut Context<Self>,
17549 ) -> Task<Result<Navigated>> {
17550 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17551 }
17552
17553 pub fn go_to_implementation(
17554 &mut self,
17555 _: &GoToImplementation,
17556 window: &mut Window,
17557 cx: &mut Context<Self>,
17558 ) -> Task<Result<Navigated>> {
17559 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17560 }
17561
17562 pub fn go_to_implementation_split(
17563 &mut self,
17564 _: &GoToImplementationSplit,
17565 window: &mut Window,
17566 cx: &mut Context<Self>,
17567 ) -> Task<Result<Navigated>> {
17568 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17569 }
17570
17571 pub fn go_to_type_definition(
17572 &mut self,
17573 _: &GoToTypeDefinition,
17574 window: &mut Window,
17575 cx: &mut Context<Self>,
17576 ) -> Task<Result<Navigated>> {
17577 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17578 }
17579
17580 pub fn go_to_definition_split(
17581 &mut self,
17582 _: &GoToDefinitionSplit,
17583 window: &mut Window,
17584 cx: &mut Context<Self>,
17585 ) -> Task<Result<Navigated>> {
17586 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17587 }
17588
17589 pub fn go_to_type_definition_split(
17590 &mut self,
17591 _: &GoToTypeDefinitionSplit,
17592 window: &mut Window,
17593 cx: &mut Context<Self>,
17594 ) -> Task<Result<Navigated>> {
17595 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17596 }
17597
17598 fn go_to_definition_of_kind(
17599 &mut self,
17600 kind: GotoDefinitionKind,
17601 split: bool,
17602 window: &mut Window,
17603 cx: &mut Context<Self>,
17604 ) -> Task<Result<Navigated>> {
17605 let Some(provider) = self.semantics_provider.clone() else {
17606 return Task::ready(Ok(Navigated::No));
17607 };
17608 let head = self
17609 .selections
17610 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17611 .head();
17612 let buffer = self.buffer.read(cx);
17613 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17614 return Task::ready(Ok(Navigated::No));
17615 };
17616 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17617 return Task::ready(Ok(Navigated::No));
17618 };
17619
17620 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17621
17622 cx.spawn_in(window, async move |editor, cx| {
17623 let Some(definitions) = definitions.await? else {
17624 return Ok(Navigated::No);
17625 };
17626 let navigated = editor
17627 .update_in(cx, |editor, window, cx| {
17628 editor.navigate_to_hover_links(
17629 Some(kind),
17630 definitions
17631 .into_iter()
17632 .filter(|location| {
17633 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17634 })
17635 .map(HoverLink::Text)
17636 .collect::<Vec<_>>(),
17637 nav_entry,
17638 split,
17639 window,
17640 cx,
17641 )
17642 })?
17643 .await?;
17644 anyhow::Ok(navigated)
17645 })
17646 }
17647
17648 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17649 let selection = self.selections.newest_anchor();
17650 let head = selection.head();
17651 let tail = selection.tail();
17652
17653 let Some((buffer, start_position)) =
17654 self.buffer.read(cx).text_anchor_for_position(head, cx)
17655 else {
17656 return;
17657 };
17658
17659 let end_position = if head != tail {
17660 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17661 return;
17662 };
17663 Some(pos)
17664 } else {
17665 None
17666 };
17667
17668 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17669 let url = if let Some(end_pos) = end_position {
17670 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17671 } else {
17672 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17673 };
17674
17675 if let Some(url) = url {
17676 cx.update(|window, cx| {
17677 if parse_zed_link(&url, cx).is_some() {
17678 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17679 } else {
17680 cx.open_url(&url);
17681 }
17682 })?;
17683 }
17684
17685 anyhow::Ok(())
17686 });
17687
17688 url_finder.detach();
17689 }
17690
17691 pub fn open_selected_filename(
17692 &mut self,
17693 _: &OpenSelectedFilename,
17694 window: &mut Window,
17695 cx: &mut Context<Self>,
17696 ) {
17697 let Some(workspace) = self.workspace() else {
17698 return;
17699 };
17700
17701 let position = self.selections.newest_anchor().head();
17702
17703 let Some((buffer, buffer_position)) =
17704 self.buffer.read(cx).text_anchor_for_position(position, cx)
17705 else {
17706 return;
17707 };
17708
17709 let project = self.project.clone();
17710
17711 cx.spawn_in(window, async move |_, cx| {
17712 let result = find_file(&buffer, project, buffer_position, cx).await;
17713
17714 if let Some((_, path)) = result {
17715 workspace
17716 .update_in(cx, |workspace, window, cx| {
17717 workspace.open_resolved_path(path, window, cx)
17718 })?
17719 .await?;
17720 }
17721 anyhow::Ok(())
17722 })
17723 .detach();
17724 }
17725
17726 pub(crate) fn navigate_to_hover_links(
17727 &mut self,
17728 kind: Option<GotoDefinitionKind>,
17729 definitions: Vec<HoverLink>,
17730 origin: Option<NavigationEntry>,
17731 split: bool,
17732 window: &mut Window,
17733 cx: &mut Context<Editor>,
17734 ) -> Task<Result<Navigated>> {
17735 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17736 let mut first_url_or_file = None;
17737 let definitions: Vec<_> = definitions
17738 .into_iter()
17739 .filter_map(|def| match def {
17740 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17741 HoverLink::InlayHint(lsp_location, server_id) => {
17742 let computation =
17743 self.compute_target_location(lsp_location, server_id, window, cx);
17744 Some(cx.background_spawn(computation))
17745 }
17746 HoverLink::Url(url) => {
17747 first_url_or_file = Some(Either::Left(url));
17748 None
17749 }
17750 HoverLink::File(path) => {
17751 first_url_or_file = Some(Either::Right(path));
17752 None
17753 }
17754 })
17755 .collect();
17756
17757 let workspace = self.workspace();
17758
17759 cx.spawn_in(window, async move |editor, cx| {
17760 let locations: Vec<Location> = future::join_all(definitions)
17761 .await
17762 .into_iter()
17763 .filter_map(|location| location.transpose())
17764 .collect::<Result<_>>()
17765 .context("location tasks")?;
17766 let mut locations = cx.update(|_, cx| {
17767 locations
17768 .into_iter()
17769 .map(|location| {
17770 let buffer = location.buffer.read(cx);
17771 (location.buffer, location.range.to_point(buffer))
17772 })
17773 .into_group_map()
17774 })?;
17775 let mut num_locations = 0;
17776 for ranges in locations.values_mut() {
17777 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17778 ranges.dedup();
17779 num_locations += ranges.len();
17780 }
17781
17782 if num_locations > 1 {
17783 let tab_kind = match kind {
17784 Some(GotoDefinitionKind::Implementation) => "Implementations",
17785 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17786 Some(GotoDefinitionKind::Declaration) => "Declarations",
17787 Some(GotoDefinitionKind::Type) => "Types",
17788 };
17789 let title = editor
17790 .update_in(cx, |_, _, cx| {
17791 let target = locations
17792 .iter()
17793 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17794 .map(|(buffer, location)| {
17795 buffer
17796 .read(cx)
17797 .text_for_range(location.clone())
17798 .collect::<String>()
17799 })
17800 .filter(|text| !text.contains('\n'))
17801 .unique()
17802 .take(3)
17803 .join(", ");
17804 if target.is_empty() {
17805 tab_kind.to_owned()
17806 } else {
17807 format!("{tab_kind} for {target}")
17808 }
17809 })
17810 .context("buffer title")?;
17811
17812 let Some(workspace) = workspace else {
17813 return Ok(Navigated::No);
17814 };
17815
17816 let opened = workspace
17817 .update_in(cx, |workspace, window, cx| {
17818 let allow_preview = PreviewTabsSettings::get_global(cx)
17819 .enable_preview_multibuffer_from_code_navigation;
17820 if let Some((target_editor, target_pane)) =
17821 Self::open_locations_in_multibuffer(
17822 workspace,
17823 locations,
17824 title,
17825 split,
17826 allow_preview,
17827 MultibufferSelectionMode::First,
17828 window,
17829 cx,
17830 )
17831 {
17832 // We create our own nav history instead of using
17833 // `target_editor.nav_history` because `nav_history`
17834 // seems to be populated asynchronously when an item
17835 // is added to a pane
17836 let mut nav_history = target_pane
17837 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17838 target_editor.update(cx, |editor, cx| {
17839 let nav_data = editor
17840 .navigation_data(editor.selections.newest_anchor().head(), cx);
17841 let target =
17842 Some(nav_history.navigation_entry(Some(
17843 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17844 )));
17845 nav_history.push_tag(origin, target);
17846 })
17847 }
17848 })
17849 .is_ok();
17850
17851 anyhow::Ok(Navigated::from_bool(opened))
17852 } else if num_locations == 0 {
17853 // If there is one url or file, open it directly
17854 match first_url_or_file {
17855 Some(Either::Left(url)) => {
17856 cx.update(|window, cx| {
17857 if parse_zed_link(&url, cx).is_some() {
17858 window
17859 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17860 } else {
17861 cx.open_url(&url);
17862 }
17863 })?;
17864 Ok(Navigated::Yes)
17865 }
17866 Some(Either::Right(path)) => {
17867 // TODO(andrew): respect preview tab settings
17868 // `enable_keep_preview_on_code_navigation` and
17869 // `enable_preview_file_from_code_navigation`
17870 let Some(workspace) = workspace else {
17871 return Ok(Navigated::No);
17872 };
17873 workspace
17874 .update_in(cx, |workspace, window, cx| {
17875 workspace.open_resolved_path(path, window, cx)
17876 })?
17877 .await?;
17878 Ok(Navigated::Yes)
17879 }
17880 None => Ok(Navigated::No),
17881 }
17882 } else {
17883 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17884 let target_range = target_ranges.first().unwrap().clone();
17885
17886 editor.update_in(cx, |editor, window, cx| {
17887 let range = editor.range_for_match(&target_range);
17888 let range = collapse_multiline_range(range);
17889
17890 if !split
17891 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17892 {
17893 editor.go_to_singleton_buffer_range(range, window, cx);
17894
17895 let target =
17896 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
17897 if let Some(mut nav_history) = editor.nav_history.clone() {
17898 nav_history.push_tag(origin, target);
17899 }
17900 } else {
17901 let Some(workspace) = workspace else {
17902 return Navigated::No;
17903 };
17904 let pane = workspace.read(cx).active_pane().clone();
17905 window.defer(cx, move |window, cx| {
17906 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
17907 workspace.update(cx, |workspace, cx| {
17908 let pane = if split {
17909 workspace.adjacent_pane(window, cx)
17910 } else {
17911 workspace.active_pane().clone()
17912 };
17913
17914 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17915 let keep_old_preview = preview_tabs_settings
17916 .enable_keep_preview_on_code_navigation;
17917 let allow_new_preview = preview_tabs_settings
17918 .enable_preview_file_from_code_navigation;
17919
17920 let editor = workspace.open_project_item(
17921 pane.clone(),
17922 target_buffer.clone(),
17923 true,
17924 true,
17925 keep_old_preview,
17926 allow_new_preview,
17927 window,
17928 cx,
17929 );
17930 (editor, pane)
17931 });
17932 // We create our own nav history instead of using
17933 // `target_editor.nav_history` because `nav_history`
17934 // seems to be populated asynchronously when an item
17935 // is added to a pane
17936 let mut nav_history = target_pane
17937 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17938 target_editor.update(cx, |target_editor, cx| {
17939 // When selecting a definition in a different buffer, disable the nav history
17940 // to avoid creating a history entry at the previous cursor location.
17941 pane.update(cx, |pane, _| pane.disable_history());
17942 target_editor.go_to_singleton_buffer_range(range, window, cx);
17943
17944 let nav_data = target_editor.navigation_data(
17945 target_editor.selections.newest_anchor().head(),
17946 cx,
17947 );
17948 let target =
17949 Some(nav_history.navigation_entry(Some(
17950 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17951 )));
17952 nav_history.push_tag(origin, target);
17953 pane.update(cx, |pane, _| pane.enable_history());
17954 });
17955 });
17956 }
17957 Navigated::Yes
17958 })
17959 }
17960 })
17961 }
17962
17963 fn compute_target_location(
17964 &self,
17965 lsp_location: lsp::Location,
17966 server_id: LanguageServerId,
17967 window: &mut Window,
17968 cx: &mut Context<Self>,
17969 ) -> Task<anyhow::Result<Option<Location>>> {
17970 let Some(project) = self.project.clone() else {
17971 return Task::ready(Ok(None));
17972 };
17973
17974 cx.spawn_in(window, async move |editor, cx| {
17975 let location_task = editor.update(cx, |_, cx| {
17976 project.update(cx, |project, cx| {
17977 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17978 })
17979 })?;
17980 let location = Some({
17981 let target_buffer_handle = location_task.await.context("open local buffer")?;
17982 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17983 let target_start = target_buffer
17984 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17985 let target_end = target_buffer
17986 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17987 target_buffer.anchor_after(target_start)
17988 ..target_buffer.anchor_before(target_end)
17989 });
17990 Location {
17991 buffer: target_buffer_handle,
17992 range,
17993 }
17994 });
17995 Ok(location)
17996 })
17997 }
17998
17999 fn go_to_next_reference(
18000 &mut self,
18001 _: &GoToNextReference,
18002 window: &mut Window,
18003 cx: &mut Context<Self>,
18004 ) {
18005 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18006 if let Some(task) = task {
18007 task.detach();
18008 };
18009 }
18010
18011 fn go_to_prev_reference(
18012 &mut self,
18013 _: &GoToPreviousReference,
18014 window: &mut Window,
18015 cx: &mut Context<Self>,
18016 ) {
18017 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18018 if let Some(task) = task {
18019 task.detach();
18020 };
18021 }
18022
18023 pub fn go_to_reference_before_or_after_position(
18024 &mut self,
18025 direction: Direction,
18026 count: usize,
18027 window: &mut Window,
18028 cx: &mut Context<Self>,
18029 ) -> Option<Task<Result<()>>> {
18030 let selection = self.selections.newest_anchor();
18031 let head = selection.head();
18032
18033 let multi_buffer = self.buffer.read(cx);
18034
18035 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18036 let workspace = self.workspace()?;
18037 let project = workspace.read(cx).project().clone();
18038 let references =
18039 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18040 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18041 let Some(locations) = references.await? else {
18042 return Ok(());
18043 };
18044
18045 if locations.is_empty() {
18046 // totally normal - the cursor may be on something which is not
18047 // a symbol (e.g. a keyword)
18048 log::info!("no references found under cursor");
18049 return Ok(());
18050 }
18051
18052 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18053
18054 let (locations, current_location_index) =
18055 multi_buffer.update(cx, |multi_buffer, cx| {
18056 let mut locations = locations
18057 .into_iter()
18058 .filter_map(|loc| {
18059 let start = multi_buffer.buffer_anchor_to_anchor(
18060 &loc.buffer,
18061 loc.range.start,
18062 cx,
18063 )?;
18064 let end = multi_buffer.buffer_anchor_to_anchor(
18065 &loc.buffer,
18066 loc.range.end,
18067 cx,
18068 )?;
18069 Some(start..end)
18070 })
18071 .collect::<Vec<_>>();
18072
18073 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18074 // There is an O(n) implementation, but given this list will be
18075 // small (usually <100 items), the extra O(log(n)) factor isn't
18076 // worth the (surprisingly large amount of) extra complexity.
18077 locations
18078 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18079
18080 let head_offset = head.to_offset(&multi_buffer_snapshot);
18081
18082 let current_location_index = locations.iter().position(|loc| {
18083 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18084 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18085 });
18086
18087 (locations, current_location_index)
18088 });
18089
18090 let Some(current_location_index) = current_location_index else {
18091 // This indicates something has gone wrong, because we already
18092 // handle the "no references" case above
18093 log::error!(
18094 "failed to find current reference under cursor. Total references: {}",
18095 locations.len()
18096 );
18097 return Ok(());
18098 };
18099
18100 let destination_location_index = match direction {
18101 Direction::Next => (current_location_index + count) % locations.len(),
18102 Direction::Prev => {
18103 (current_location_index + locations.len() - count % locations.len())
18104 % locations.len()
18105 }
18106 };
18107
18108 // TODO(cameron): is this needed?
18109 // the thinking is to avoid "jumping to the current location" (avoid
18110 // polluting "jumplist" in vim terms)
18111 if current_location_index == destination_location_index {
18112 return Ok(());
18113 }
18114
18115 let Range { start, end } = locations[destination_location_index];
18116
18117 editor.update_in(cx, |editor, window, cx| {
18118 let effects = SelectionEffects::default();
18119
18120 editor.unfold_ranges(&[start..end], false, false, cx);
18121 editor.change_selections(effects, window, cx, |s| {
18122 s.select_ranges([start..start]);
18123 });
18124 })?;
18125
18126 Ok(())
18127 }))
18128 }
18129
18130 pub fn find_all_references(
18131 &mut self,
18132 action: &FindAllReferences,
18133 window: &mut Window,
18134 cx: &mut Context<Self>,
18135 ) -> Option<Task<Result<Navigated>>> {
18136 let always_open_multibuffer = action.always_open_multibuffer;
18137 let selection = self.selections.newest_anchor();
18138 let multi_buffer = self.buffer.read(cx);
18139 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18140 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18141 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18142 let head = selection_offset.head();
18143
18144 let head_anchor = multi_buffer_snapshot.anchor_at(
18145 head,
18146 if head < selection_offset.tail() {
18147 Bias::Right
18148 } else {
18149 Bias::Left
18150 },
18151 );
18152
18153 match self
18154 .find_all_references_task_sources
18155 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18156 {
18157 Ok(_) => {
18158 log::info!(
18159 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18160 );
18161 return None;
18162 }
18163 Err(i) => {
18164 self.find_all_references_task_sources.insert(i, head_anchor);
18165 }
18166 }
18167
18168 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18169 let workspace = self.workspace()?;
18170 let project = workspace.read(cx).project().clone();
18171 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18172 Some(cx.spawn_in(window, async move |editor, cx| {
18173 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18174 if let Ok(i) = editor
18175 .find_all_references_task_sources
18176 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18177 {
18178 editor.find_all_references_task_sources.remove(i);
18179 }
18180 });
18181
18182 let Some(locations) = references.await? else {
18183 return anyhow::Ok(Navigated::No);
18184 };
18185 let mut locations = cx.update(|_, cx| {
18186 locations
18187 .into_iter()
18188 .map(|location| {
18189 let buffer = location.buffer.read(cx);
18190 (location.buffer, location.range.to_point(buffer))
18191 })
18192 // if special-casing the single-match case, remove ranges
18193 // that intersect current selection
18194 .filter(|(location_buffer, location)| {
18195 if always_open_multibuffer || &buffer != location_buffer {
18196 return true;
18197 }
18198
18199 !location.contains_inclusive(&selection_point.range())
18200 })
18201 .into_group_map()
18202 })?;
18203 if locations.is_empty() {
18204 return anyhow::Ok(Navigated::No);
18205 }
18206 for ranges in locations.values_mut() {
18207 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18208 ranges.dedup();
18209 }
18210 let mut num_locations = 0;
18211 for ranges in locations.values_mut() {
18212 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18213 ranges.dedup();
18214 num_locations += ranges.len();
18215 }
18216
18217 if num_locations == 1 && !always_open_multibuffer {
18218 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18219 let target_range = target_ranges.first().unwrap().clone();
18220
18221 return editor.update_in(cx, |editor, window, cx| {
18222 let range = target_range.to_point(target_buffer.read(cx));
18223 let range = editor.range_for_match(&range);
18224 let range = range.start..range.start;
18225
18226 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18227 editor.go_to_singleton_buffer_range(range, window, cx);
18228 } else {
18229 let pane = workspace.read(cx).active_pane().clone();
18230 window.defer(cx, move |window, cx| {
18231 let target_editor: Entity<Self> =
18232 workspace.update(cx, |workspace, cx| {
18233 let pane = workspace.active_pane().clone();
18234
18235 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18236 let keep_old_preview = preview_tabs_settings
18237 .enable_keep_preview_on_code_navigation;
18238 let allow_new_preview = preview_tabs_settings
18239 .enable_preview_file_from_code_navigation;
18240
18241 workspace.open_project_item(
18242 pane,
18243 target_buffer.clone(),
18244 true,
18245 true,
18246 keep_old_preview,
18247 allow_new_preview,
18248 window,
18249 cx,
18250 )
18251 });
18252 target_editor.update(cx, |target_editor, cx| {
18253 // When selecting a definition in a different buffer, disable the nav history
18254 // to avoid creating a history entry at the previous cursor location.
18255 pane.update(cx, |pane, _| pane.disable_history());
18256 target_editor.go_to_singleton_buffer_range(range, window, cx);
18257 pane.update(cx, |pane, _| pane.enable_history());
18258 });
18259 });
18260 }
18261 Navigated::No
18262 });
18263 }
18264
18265 workspace.update_in(cx, |workspace, window, cx| {
18266 let target = locations
18267 .iter()
18268 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18269 .map(|(buffer, location)| {
18270 buffer
18271 .read(cx)
18272 .text_for_range(location.clone())
18273 .collect::<String>()
18274 })
18275 .filter(|text| !text.contains('\n'))
18276 .unique()
18277 .take(3)
18278 .join(", ");
18279 let title = if target.is_empty() {
18280 "References".to_owned()
18281 } else {
18282 format!("References to {target}")
18283 };
18284 let allow_preview = PreviewTabsSettings::get_global(cx)
18285 .enable_preview_multibuffer_from_code_navigation;
18286 Self::open_locations_in_multibuffer(
18287 workspace,
18288 locations,
18289 title,
18290 false,
18291 allow_preview,
18292 MultibufferSelectionMode::First,
18293 window,
18294 cx,
18295 );
18296 Navigated::Yes
18297 })
18298 }))
18299 }
18300
18301 /// Opens a multibuffer with the given project locations in it.
18302 pub fn open_locations_in_multibuffer(
18303 workspace: &mut Workspace,
18304 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18305 title: String,
18306 split: bool,
18307 allow_preview: bool,
18308 multibuffer_selection_mode: MultibufferSelectionMode,
18309 window: &mut Window,
18310 cx: &mut Context<Workspace>,
18311 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18312 if locations.is_empty() {
18313 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18314 return None;
18315 }
18316
18317 let capability = workspace.project().read(cx).capability();
18318 let mut ranges = <Vec<Range<Anchor>>>::new();
18319
18320 // a key to find existing multibuffer editors with the same set of locations
18321 // to prevent us from opening more and more multibuffer tabs for searches and the like
18322 let mut key = (title.clone(), vec![]);
18323 let excerpt_buffer = cx.new(|cx| {
18324 let key = &mut key.1;
18325 let mut multibuffer = MultiBuffer::new(capability);
18326 for (buffer, mut ranges_for_buffer) in locations {
18327 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18328 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18329 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18330 PathKey::for_buffer(&buffer, cx),
18331 buffer.clone(),
18332 ranges_for_buffer,
18333 multibuffer_context_lines(cx),
18334 cx,
18335 );
18336 ranges.extend(new_ranges)
18337 }
18338
18339 multibuffer.with_title(title)
18340 });
18341 let existing = workspace.active_pane().update(cx, |pane, cx| {
18342 pane.items()
18343 .filter_map(|item| item.downcast::<Editor>())
18344 .find(|editor| {
18345 editor
18346 .read(cx)
18347 .lookup_key
18348 .as_ref()
18349 .and_then(|it| {
18350 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18351 })
18352 .is_some_and(|it| *it == key)
18353 })
18354 });
18355 let was_existing = existing.is_some();
18356 let editor = existing.unwrap_or_else(|| {
18357 cx.new(|cx| {
18358 let mut editor = Editor::for_multibuffer(
18359 excerpt_buffer,
18360 Some(workspace.project().clone()),
18361 window,
18362 cx,
18363 );
18364 editor.lookup_key = Some(Box::new(key));
18365 editor
18366 })
18367 });
18368 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18369 MultibufferSelectionMode::First => {
18370 if let Some(first_range) = ranges.first() {
18371 editor.change_selections(
18372 SelectionEffects::no_scroll(),
18373 window,
18374 cx,
18375 |selections| {
18376 selections.clear_disjoint();
18377 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18378 },
18379 );
18380 }
18381 editor.highlight_background::<Self>(
18382 &ranges,
18383 |_, theme| theme.colors().editor_highlighted_line_background,
18384 cx,
18385 );
18386 }
18387 MultibufferSelectionMode::All => {
18388 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18389 selections.clear_disjoint();
18390 selections.select_anchor_ranges(ranges);
18391 });
18392 }
18393 });
18394
18395 let item = Box::new(editor.clone());
18396
18397 let pane = if split {
18398 workspace.adjacent_pane(window, cx)
18399 } else {
18400 workspace.active_pane().clone()
18401 };
18402 let activate_pane = split;
18403
18404 let mut destination_index = None;
18405 pane.update(cx, |pane, cx| {
18406 if allow_preview && !was_existing {
18407 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18408 }
18409 if was_existing && !allow_preview {
18410 pane.unpreview_item_if_preview(item.item_id());
18411 }
18412 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18413 });
18414
18415 Some((editor, pane))
18416 }
18417
18418 pub fn rename(
18419 &mut self,
18420 _: &Rename,
18421 window: &mut Window,
18422 cx: &mut Context<Self>,
18423 ) -> Option<Task<Result<()>>> {
18424 use language::ToOffset as _;
18425
18426 let provider = self.semantics_provider.clone()?;
18427 let selection = self.selections.newest_anchor().clone();
18428 let (cursor_buffer, cursor_buffer_position) = self
18429 .buffer
18430 .read(cx)
18431 .text_anchor_for_position(selection.head(), cx)?;
18432 let (tail_buffer, cursor_buffer_position_end) = self
18433 .buffer
18434 .read(cx)
18435 .text_anchor_for_position(selection.tail(), cx)?;
18436 if tail_buffer != cursor_buffer {
18437 return None;
18438 }
18439
18440 let snapshot = cursor_buffer.read(cx).snapshot();
18441 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18442 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18443 let prepare_rename = provider
18444 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18445 .unwrap_or_else(|| Task::ready(Ok(None)));
18446 drop(snapshot);
18447
18448 Some(cx.spawn_in(window, async move |this, cx| {
18449 let rename_range = if let Some(range) = prepare_rename.await? {
18450 Some(range)
18451 } else {
18452 this.update(cx, |this, cx| {
18453 let buffer = this.buffer.read(cx).snapshot(cx);
18454 let mut buffer_highlights = this
18455 .document_highlights_for_position(selection.head(), &buffer)
18456 .filter(|highlight| {
18457 highlight.start.excerpt_id == selection.head().excerpt_id
18458 && highlight.end.excerpt_id == selection.head().excerpt_id
18459 });
18460 buffer_highlights
18461 .next()
18462 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18463 })?
18464 };
18465 if let Some(rename_range) = rename_range {
18466 this.update_in(cx, |this, window, cx| {
18467 let snapshot = cursor_buffer.read(cx).snapshot();
18468 let rename_buffer_range = rename_range.to_offset(&snapshot);
18469 let cursor_offset_in_rename_range =
18470 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18471 let cursor_offset_in_rename_range_end =
18472 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18473
18474 this.take_rename(false, window, cx);
18475 let buffer = this.buffer.read(cx).read(cx);
18476 let cursor_offset = selection.head().to_offset(&buffer);
18477 let rename_start =
18478 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18479 let rename_end = rename_start + rename_buffer_range.len();
18480 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18481 let mut old_highlight_id = None;
18482 let old_name: Arc<str> = buffer
18483 .chunks(rename_start..rename_end, true)
18484 .map(|chunk| {
18485 if old_highlight_id.is_none() {
18486 old_highlight_id = chunk.syntax_highlight_id;
18487 }
18488 chunk.text
18489 })
18490 .collect::<String>()
18491 .into();
18492
18493 drop(buffer);
18494
18495 // Position the selection in the rename editor so that it matches the current selection.
18496 this.show_local_selections = false;
18497 let rename_editor = cx.new(|cx| {
18498 let mut editor = Editor::single_line(window, cx);
18499 editor.buffer.update(cx, |buffer, cx| {
18500 buffer.edit(
18501 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18502 None,
18503 cx,
18504 )
18505 });
18506 let cursor_offset_in_rename_range =
18507 MultiBufferOffset(cursor_offset_in_rename_range);
18508 let cursor_offset_in_rename_range_end =
18509 MultiBufferOffset(cursor_offset_in_rename_range_end);
18510 let rename_selection_range = match cursor_offset_in_rename_range
18511 .cmp(&cursor_offset_in_rename_range_end)
18512 {
18513 Ordering::Equal => {
18514 editor.select_all(&SelectAll, window, cx);
18515 return editor;
18516 }
18517 Ordering::Less => {
18518 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18519 }
18520 Ordering::Greater => {
18521 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18522 }
18523 };
18524 if rename_selection_range.end.0 > old_name.len() {
18525 editor.select_all(&SelectAll, window, cx);
18526 } else {
18527 editor.change_selections(Default::default(), window, cx, |s| {
18528 s.select_ranges([rename_selection_range]);
18529 });
18530 }
18531 editor
18532 });
18533 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18534 if e == &EditorEvent::Focused {
18535 cx.emit(EditorEvent::FocusedIn)
18536 }
18537 })
18538 .detach();
18539
18540 let write_highlights =
18541 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18542 let read_highlights =
18543 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18544 let ranges = write_highlights
18545 .iter()
18546 .flat_map(|(_, ranges)| ranges.iter())
18547 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18548 .cloned()
18549 .collect();
18550
18551 this.highlight_text::<Rename>(
18552 ranges,
18553 HighlightStyle {
18554 fade_out: Some(0.6),
18555 ..Default::default()
18556 },
18557 cx,
18558 );
18559 let rename_focus_handle = rename_editor.focus_handle(cx);
18560 window.focus(&rename_focus_handle, cx);
18561 let block_id = this.insert_blocks(
18562 [BlockProperties {
18563 style: BlockStyle::Flex,
18564 placement: BlockPlacement::Below(range.start),
18565 height: Some(1),
18566 render: Arc::new({
18567 let rename_editor = rename_editor.clone();
18568 move |cx: &mut BlockContext| {
18569 let mut text_style = cx.editor_style.text.clone();
18570 if let Some(highlight_style) = old_highlight_id
18571 .and_then(|h| h.style(&cx.editor_style.syntax))
18572 {
18573 text_style = text_style.highlight(highlight_style);
18574 }
18575 div()
18576 .block_mouse_except_scroll()
18577 .pl(cx.anchor_x)
18578 .child(EditorElement::new(
18579 &rename_editor,
18580 EditorStyle {
18581 background: cx.theme().system().transparent,
18582 local_player: cx.editor_style.local_player,
18583 text: text_style,
18584 scrollbar_width: cx.editor_style.scrollbar_width,
18585 syntax: cx.editor_style.syntax.clone(),
18586 status: cx.editor_style.status.clone(),
18587 inlay_hints_style: HighlightStyle {
18588 font_weight: Some(FontWeight::BOLD),
18589 ..make_inlay_hints_style(cx.app)
18590 },
18591 edit_prediction_styles: make_suggestion_styles(
18592 cx.app,
18593 ),
18594 ..EditorStyle::default()
18595 },
18596 ))
18597 .into_any_element()
18598 }
18599 }),
18600 priority: 0,
18601 }],
18602 Some(Autoscroll::fit()),
18603 cx,
18604 )[0];
18605 this.pending_rename = Some(RenameState {
18606 range,
18607 old_name,
18608 editor: rename_editor,
18609 block_id,
18610 });
18611 })?;
18612 }
18613
18614 Ok(())
18615 }))
18616 }
18617
18618 pub fn confirm_rename(
18619 &mut self,
18620 _: &ConfirmRename,
18621 window: &mut Window,
18622 cx: &mut Context<Self>,
18623 ) -> Option<Task<Result<()>>> {
18624 let rename = self.take_rename(false, window, cx)?;
18625 let workspace = self.workspace()?.downgrade();
18626 let (buffer, start) = self
18627 .buffer
18628 .read(cx)
18629 .text_anchor_for_position(rename.range.start, cx)?;
18630 let (end_buffer, _) = self
18631 .buffer
18632 .read(cx)
18633 .text_anchor_for_position(rename.range.end, cx)?;
18634 if buffer != end_buffer {
18635 return None;
18636 }
18637
18638 let old_name = rename.old_name;
18639 let new_name = rename.editor.read(cx).text(cx);
18640
18641 let rename = self.semantics_provider.as_ref()?.perform_rename(
18642 &buffer,
18643 start,
18644 new_name.clone(),
18645 cx,
18646 )?;
18647
18648 Some(cx.spawn_in(window, async move |editor, cx| {
18649 let project_transaction = rename.await?;
18650 Self::open_project_transaction(
18651 &editor,
18652 workspace,
18653 project_transaction,
18654 format!("Rename: {} → {}", old_name, new_name),
18655 cx,
18656 )
18657 .await?;
18658
18659 editor.update(cx, |editor, cx| {
18660 editor.refresh_document_highlights(cx);
18661 })?;
18662 Ok(())
18663 }))
18664 }
18665
18666 fn take_rename(
18667 &mut self,
18668 moving_cursor: bool,
18669 window: &mut Window,
18670 cx: &mut Context<Self>,
18671 ) -> Option<RenameState> {
18672 let rename = self.pending_rename.take()?;
18673 if rename.editor.focus_handle(cx).is_focused(window) {
18674 window.focus(&self.focus_handle, cx);
18675 }
18676
18677 self.remove_blocks(
18678 [rename.block_id].into_iter().collect(),
18679 Some(Autoscroll::fit()),
18680 cx,
18681 );
18682 self.clear_highlights::<Rename>(cx);
18683 self.show_local_selections = true;
18684
18685 if moving_cursor {
18686 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18687 editor
18688 .selections
18689 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18690 .head()
18691 });
18692
18693 // Update the selection to match the position of the selection inside
18694 // the rename editor.
18695 let snapshot = self.buffer.read(cx).read(cx);
18696 let rename_range = rename.range.to_offset(&snapshot);
18697 let cursor_in_editor = snapshot
18698 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18699 .min(rename_range.end);
18700 drop(snapshot);
18701
18702 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18703 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18704 });
18705 } else {
18706 self.refresh_document_highlights(cx);
18707 }
18708
18709 Some(rename)
18710 }
18711
18712 pub fn pending_rename(&self) -> Option<&RenameState> {
18713 self.pending_rename.as_ref()
18714 }
18715
18716 fn format(
18717 &mut self,
18718 _: &Format,
18719 window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) -> Option<Task<Result<()>>> {
18722 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18723
18724 let project = match &self.project {
18725 Some(project) => project.clone(),
18726 None => return None,
18727 };
18728
18729 Some(self.perform_format(
18730 project,
18731 FormatTrigger::Manual,
18732 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18733 window,
18734 cx,
18735 ))
18736 }
18737
18738 fn format_selections(
18739 &mut self,
18740 _: &FormatSelections,
18741 window: &mut Window,
18742 cx: &mut Context<Self>,
18743 ) -> Option<Task<Result<()>>> {
18744 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18745
18746 let project = match &self.project {
18747 Some(project) => project.clone(),
18748 None => return None,
18749 };
18750
18751 let ranges = self
18752 .selections
18753 .all_adjusted(&self.display_snapshot(cx))
18754 .into_iter()
18755 .map(|selection| selection.range())
18756 .collect_vec();
18757
18758 Some(self.perform_format(
18759 project,
18760 FormatTrigger::Manual,
18761 FormatTarget::Ranges(ranges),
18762 window,
18763 cx,
18764 ))
18765 }
18766
18767 fn perform_format(
18768 &mut self,
18769 project: Entity<Project>,
18770 trigger: FormatTrigger,
18771 target: FormatTarget,
18772 window: &mut Window,
18773 cx: &mut Context<Self>,
18774 ) -> Task<Result<()>> {
18775 let buffer = self.buffer.clone();
18776 let (buffers, target) = match target {
18777 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18778 FormatTarget::Ranges(selection_ranges) => {
18779 let multi_buffer = buffer.read(cx);
18780 let snapshot = multi_buffer.read(cx);
18781 let mut buffers = HashSet::default();
18782 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18783 BTreeMap::new();
18784 for selection_range in selection_ranges {
18785 for (buffer, buffer_range, _) in
18786 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
18787 {
18788 let buffer_id = buffer.remote_id();
18789 let start = buffer.anchor_before(buffer_range.start);
18790 let end = buffer.anchor_after(buffer_range.end);
18791 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18792 buffer_id_to_ranges
18793 .entry(buffer_id)
18794 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18795 .or_insert_with(|| vec![start..end]);
18796 }
18797 }
18798 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18799 }
18800 };
18801
18802 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18803 let selections_prev = transaction_id_prev
18804 .and_then(|transaction_id_prev| {
18805 // default to selections as they were after the last edit, if we have them,
18806 // instead of how they are now.
18807 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18808 // will take you back to where you made the last edit, instead of staying where you scrolled
18809 self.selection_history
18810 .transaction(transaction_id_prev)
18811 .map(|t| t.0.clone())
18812 })
18813 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18814
18815 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18816 let format = project.update(cx, |project, cx| {
18817 project.format(buffers, target, true, trigger, cx)
18818 });
18819
18820 cx.spawn_in(window, async move |editor, cx| {
18821 let transaction = futures::select_biased! {
18822 transaction = format.log_err().fuse() => transaction,
18823 () = timeout => {
18824 log::warn!("timed out waiting for formatting");
18825 None
18826 }
18827 };
18828
18829 buffer.update(cx, |buffer, cx| {
18830 if let Some(transaction) = transaction
18831 && !buffer.is_singleton()
18832 {
18833 buffer.push_transaction(&transaction.0, cx);
18834 }
18835 cx.notify();
18836 });
18837
18838 if let Some(transaction_id_now) =
18839 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18840 {
18841 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18842 if has_new_transaction {
18843 editor
18844 .update(cx, |editor, _| {
18845 editor
18846 .selection_history
18847 .insert_transaction(transaction_id_now, selections_prev);
18848 })
18849 .ok();
18850 }
18851 }
18852
18853 Ok(())
18854 })
18855 }
18856
18857 fn organize_imports(
18858 &mut self,
18859 _: &OrganizeImports,
18860 window: &mut Window,
18861 cx: &mut Context<Self>,
18862 ) -> Option<Task<Result<()>>> {
18863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18864 let project = match &self.project {
18865 Some(project) => project.clone(),
18866 None => return None,
18867 };
18868 Some(self.perform_code_action_kind(
18869 project,
18870 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18871 window,
18872 cx,
18873 ))
18874 }
18875
18876 fn perform_code_action_kind(
18877 &mut self,
18878 project: Entity<Project>,
18879 kind: CodeActionKind,
18880 window: &mut Window,
18881 cx: &mut Context<Self>,
18882 ) -> Task<Result<()>> {
18883 let buffer = self.buffer.clone();
18884 let buffers = buffer.read(cx).all_buffers();
18885 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18886 let apply_action = project.update(cx, |project, cx| {
18887 project.apply_code_action_kind(buffers, kind, true, cx)
18888 });
18889 cx.spawn_in(window, async move |_, cx| {
18890 let transaction = futures::select_biased! {
18891 () = timeout => {
18892 log::warn!("timed out waiting for executing code action");
18893 None
18894 }
18895 transaction = apply_action.log_err().fuse() => transaction,
18896 };
18897 buffer.update(cx, |buffer, cx| {
18898 // check if we need this
18899 if let Some(transaction) = transaction
18900 && !buffer.is_singleton()
18901 {
18902 buffer.push_transaction(&transaction.0, cx);
18903 }
18904 cx.notify();
18905 });
18906 Ok(())
18907 })
18908 }
18909
18910 pub fn restart_language_server(
18911 &mut self,
18912 _: &RestartLanguageServer,
18913 _: &mut Window,
18914 cx: &mut Context<Self>,
18915 ) {
18916 if let Some(project) = self.project.clone() {
18917 self.buffer.update(cx, |multi_buffer, cx| {
18918 project.update(cx, |project, cx| {
18919 project.restart_language_servers_for_buffers(
18920 multi_buffer.all_buffers().into_iter().collect(),
18921 HashSet::default(),
18922 cx,
18923 );
18924 });
18925 })
18926 }
18927 }
18928
18929 pub fn stop_language_server(
18930 &mut self,
18931 _: &StopLanguageServer,
18932 _: &mut Window,
18933 cx: &mut Context<Self>,
18934 ) {
18935 if let Some(project) = self.project.clone() {
18936 self.buffer.update(cx, |multi_buffer, cx| {
18937 project.update(cx, |project, cx| {
18938 project.stop_language_servers_for_buffers(
18939 multi_buffer.all_buffers().into_iter().collect(),
18940 HashSet::default(),
18941 cx,
18942 );
18943 });
18944 });
18945 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18946 }
18947 }
18948
18949 fn cancel_language_server_work(
18950 workspace: &mut Workspace,
18951 _: &actions::CancelLanguageServerWork,
18952 _: &mut Window,
18953 cx: &mut Context<Workspace>,
18954 ) {
18955 let project = workspace.project();
18956 let buffers = workspace
18957 .active_item(cx)
18958 .and_then(|item| item.act_as::<Editor>(cx))
18959 .map_or(HashSet::default(), |editor| {
18960 editor.read(cx).buffer.read(cx).all_buffers()
18961 });
18962 project.update(cx, |project, cx| {
18963 project.cancel_language_server_work_for_buffers(buffers, cx);
18964 });
18965 }
18966
18967 fn show_character_palette(
18968 &mut self,
18969 _: &ShowCharacterPalette,
18970 window: &mut Window,
18971 _: &mut Context<Self>,
18972 ) {
18973 window.show_character_palette();
18974 }
18975
18976 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18977 if !self.diagnostics_enabled() {
18978 return;
18979 }
18980
18981 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18982 let buffer = self.buffer.read(cx).snapshot(cx);
18983 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18984 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18985 let is_valid = buffer
18986 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18987 .any(|entry| {
18988 entry.diagnostic.is_primary
18989 && !entry.range.is_empty()
18990 && entry.range.start == primary_range_start
18991 && entry.diagnostic.message == active_diagnostics.active_message
18992 });
18993
18994 if !is_valid {
18995 self.dismiss_diagnostics(cx);
18996 }
18997 }
18998 }
18999
19000 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19001 match &self.active_diagnostics {
19002 ActiveDiagnostic::Group(group) => Some(group),
19003 _ => None,
19004 }
19005 }
19006
19007 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19008 if !self.diagnostics_enabled() {
19009 return;
19010 }
19011 self.dismiss_diagnostics(cx);
19012 self.active_diagnostics = ActiveDiagnostic::All;
19013 }
19014
19015 fn activate_diagnostics(
19016 &mut self,
19017 buffer_id: BufferId,
19018 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19019 window: &mut Window,
19020 cx: &mut Context<Self>,
19021 ) {
19022 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19023 return;
19024 }
19025 self.dismiss_diagnostics(cx);
19026 let snapshot = self.snapshot(window, cx);
19027 let buffer = self.buffer.read(cx).snapshot(cx);
19028 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19029 return;
19030 };
19031
19032 let diagnostic_group = buffer
19033 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19034 .collect::<Vec<_>>();
19035
19036 let language_registry = self
19037 .project()
19038 .map(|project| project.read(cx).languages().clone());
19039
19040 let blocks = renderer.render_group(
19041 diagnostic_group,
19042 buffer_id,
19043 snapshot,
19044 cx.weak_entity(),
19045 language_registry,
19046 cx,
19047 );
19048
19049 let blocks = self.display_map.update(cx, |display_map, cx| {
19050 display_map.insert_blocks(blocks, cx).into_iter().collect()
19051 });
19052 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19053 active_range: buffer.anchor_before(diagnostic.range.start)
19054 ..buffer.anchor_after(diagnostic.range.end),
19055 active_message: diagnostic.diagnostic.message.clone(),
19056 group_id: diagnostic.diagnostic.group_id,
19057 blocks,
19058 });
19059 cx.notify();
19060 }
19061
19062 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19063 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19064 return;
19065 };
19066
19067 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19068 if let ActiveDiagnostic::Group(group) = prev {
19069 self.display_map.update(cx, |display_map, cx| {
19070 display_map.remove_blocks(group.blocks, cx);
19071 });
19072 cx.notify();
19073 }
19074 }
19075
19076 /// Disable inline diagnostics rendering for this editor.
19077 pub fn disable_inline_diagnostics(&mut self) {
19078 self.inline_diagnostics_enabled = false;
19079 self.inline_diagnostics_update = Task::ready(());
19080 self.inline_diagnostics.clear();
19081 }
19082
19083 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19084 self.diagnostics_enabled = false;
19085 self.dismiss_diagnostics(cx);
19086 self.inline_diagnostics_update = Task::ready(());
19087 self.inline_diagnostics.clear();
19088 }
19089
19090 pub fn disable_word_completions(&mut self) {
19091 self.word_completions_enabled = false;
19092 }
19093
19094 pub fn diagnostics_enabled(&self) -> bool {
19095 self.diagnostics_enabled && self.mode.is_full()
19096 }
19097
19098 pub fn inline_diagnostics_enabled(&self) -> bool {
19099 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19100 }
19101
19102 pub fn show_inline_diagnostics(&self) -> bool {
19103 self.show_inline_diagnostics
19104 }
19105
19106 pub fn toggle_inline_diagnostics(
19107 &mut self,
19108 _: &ToggleInlineDiagnostics,
19109 window: &mut Window,
19110 cx: &mut Context<Editor>,
19111 ) {
19112 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19113 self.refresh_inline_diagnostics(false, window, cx);
19114 }
19115
19116 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19117 self.diagnostics_max_severity = severity;
19118 self.display_map.update(cx, |display_map, _| {
19119 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19120 });
19121 }
19122
19123 pub fn toggle_diagnostics(
19124 &mut self,
19125 _: &ToggleDiagnostics,
19126 window: &mut Window,
19127 cx: &mut Context<Editor>,
19128 ) {
19129 if !self.diagnostics_enabled() {
19130 return;
19131 }
19132
19133 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19134 EditorSettings::get_global(cx)
19135 .diagnostics_max_severity
19136 .filter(|severity| severity != &DiagnosticSeverity::Off)
19137 .unwrap_or(DiagnosticSeverity::Hint)
19138 } else {
19139 DiagnosticSeverity::Off
19140 };
19141 self.set_max_diagnostics_severity(new_severity, cx);
19142 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19143 self.active_diagnostics = ActiveDiagnostic::None;
19144 self.inline_diagnostics_update = Task::ready(());
19145 self.inline_diagnostics.clear();
19146 } else {
19147 self.refresh_inline_diagnostics(false, window, cx);
19148 }
19149
19150 cx.notify();
19151 }
19152
19153 pub fn toggle_minimap(
19154 &mut self,
19155 _: &ToggleMinimap,
19156 window: &mut Window,
19157 cx: &mut Context<Editor>,
19158 ) {
19159 if self.supports_minimap(cx) {
19160 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19161 }
19162 }
19163
19164 fn refresh_inline_diagnostics(
19165 &mut self,
19166 debounce: bool,
19167 window: &mut Window,
19168 cx: &mut Context<Self>,
19169 ) {
19170 let max_severity = ProjectSettings::get_global(cx)
19171 .diagnostics
19172 .inline
19173 .max_severity
19174 .unwrap_or(self.diagnostics_max_severity);
19175
19176 if !self.inline_diagnostics_enabled()
19177 || !self.diagnostics_enabled()
19178 || !self.show_inline_diagnostics
19179 || max_severity == DiagnosticSeverity::Off
19180 {
19181 self.inline_diagnostics_update = Task::ready(());
19182 self.inline_diagnostics.clear();
19183 return;
19184 }
19185
19186 let debounce_ms = ProjectSettings::get_global(cx)
19187 .diagnostics
19188 .inline
19189 .update_debounce_ms;
19190 let debounce = if debounce && debounce_ms > 0 {
19191 Some(Duration::from_millis(debounce_ms))
19192 } else {
19193 None
19194 };
19195 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19196 if let Some(debounce) = debounce {
19197 cx.background_executor().timer(debounce).await;
19198 }
19199 let Some(snapshot) = editor.upgrade().map(|editor| {
19200 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19201 }) else {
19202 return;
19203 };
19204
19205 let new_inline_diagnostics = cx
19206 .background_spawn(async move {
19207 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19208 for diagnostic_entry in
19209 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19210 {
19211 let message = diagnostic_entry
19212 .diagnostic
19213 .message
19214 .split_once('\n')
19215 .map(|(line, _)| line)
19216 .map(SharedString::new)
19217 .unwrap_or_else(|| {
19218 SharedString::new(&*diagnostic_entry.diagnostic.message)
19219 });
19220 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19221 let (Ok(i) | Err(i)) = inline_diagnostics
19222 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19223 inline_diagnostics.insert(
19224 i,
19225 (
19226 start_anchor,
19227 InlineDiagnostic {
19228 message,
19229 group_id: diagnostic_entry.diagnostic.group_id,
19230 start: diagnostic_entry.range.start.to_point(&snapshot),
19231 is_primary: diagnostic_entry.diagnostic.is_primary,
19232 severity: diagnostic_entry.diagnostic.severity,
19233 },
19234 ),
19235 );
19236 }
19237 inline_diagnostics
19238 })
19239 .await;
19240
19241 editor
19242 .update(cx, |editor, cx| {
19243 editor.inline_diagnostics = new_inline_diagnostics;
19244 cx.notify();
19245 })
19246 .ok();
19247 });
19248 }
19249
19250 fn pull_diagnostics(
19251 &mut self,
19252 buffer_id: BufferId,
19253 _window: &Window,
19254 cx: &mut Context<Self>,
19255 ) -> Option<()> {
19256 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
19257 return None;
19258 }
19259 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19260 .diagnostics
19261 .lsp_pull_diagnostics;
19262 if !pull_diagnostics_settings.enabled {
19263 return None;
19264 }
19265 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19266 let project = self.project()?.downgrade();
19267 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19268
19269 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19270 cx.background_executor().timer(debounce).await;
19271 if let Ok(task) = project.update(cx, |project, cx| {
19272 project.lsp_store().update(cx, |lsp_store, cx| {
19273 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19274 })
19275 }) {
19276 task.await.log_err();
19277 }
19278 project
19279 .update(cx, |project, cx| {
19280 project.lsp_store().update(cx, |lsp_store, cx| {
19281 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19282 })
19283 })
19284 .log_err();
19285 });
19286
19287 Some(())
19288 }
19289
19290 pub fn set_selections_from_remote(
19291 &mut self,
19292 selections: Vec<Selection<Anchor>>,
19293 pending_selection: Option<Selection<Anchor>>,
19294 window: &mut Window,
19295 cx: &mut Context<Self>,
19296 ) {
19297 let old_cursor_position = self.selections.newest_anchor().head();
19298 self.selections
19299 .change_with(&self.display_snapshot(cx), |s| {
19300 s.select_anchors(selections);
19301 if let Some(pending_selection) = pending_selection {
19302 s.set_pending(pending_selection, SelectMode::Character);
19303 } else {
19304 s.clear_pending();
19305 }
19306 });
19307 self.selections_did_change(
19308 false,
19309 &old_cursor_position,
19310 SelectionEffects::default(),
19311 window,
19312 cx,
19313 );
19314 }
19315
19316 pub fn transact(
19317 &mut self,
19318 window: &mut Window,
19319 cx: &mut Context<Self>,
19320 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19321 ) -> Option<TransactionId> {
19322 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19323 this.start_transaction_at(Instant::now(), window, cx);
19324 update(this, window, cx);
19325 this.end_transaction_at(Instant::now(), cx)
19326 })
19327 }
19328
19329 pub fn start_transaction_at(
19330 &mut self,
19331 now: Instant,
19332 window: &mut Window,
19333 cx: &mut Context<Self>,
19334 ) -> Option<TransactionId> {
19335 self.end_selection(window, cx);
19336 if let Some(tx_id) = self
19337 .buffer
19338 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19339 {
19340 self.selection_history
19341 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19342 cx.emit(EditorEvent::TransactionBegun {
19343 transaction_id: tx_id,
19344 });
19345 Some(tx_id)
19346 } else {
19347 None
19348 }
19349 }
19350
19351 pub fn end_transaction_at(
19352 &mut self,
19353 now: Instant,
19354 cx: &mut Context<Self>,
19355 ) -> Option<TransactionId> {
19356 if let Some(transaction_id) = self
19357 .buffer
19358 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19359 {
19360 if let Some((_, end_selections)) =
19361 self.selection_history.transaction_mut(transaction_id)
19362 {
19363 *end_selections = Some(self.selections.disjoint_anchors_arc());
19364 } else {
19365 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19366 }
19367
19368 cx.emit(EditorEvent::Edited { transaction_id });
19369 Some(transaction_id)
19370 } else {
19371 None
19372 }
19373 }
19374
19375 pub fn modify_transaction_selection_history(
19376 &mut self,
19377 transaction_id: TransactionId,
19378 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19379 ) -> bool {
19380 self.selection_history
19381 .transaction_mut(transaction_id)
19382 .map(modify)
19383 .is_some()
19384 }
19385
19386 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19387 if self.selection_mark_mode {
19388 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19389 s.move_with(|_, sel| {
19390 sel.collapse_to(sel.head(), SelectionGoal::None);
19391 });
19392 })
19393 }
19394 self.selection_mark_mode = true;
19395 cx.notify();
19396 }
19397
19398 pub fn swap_selection_ends(
19399 &mut self,
19400 _: &actions::SwapSelectionEnds,
19401 window: &mut Window,
19402 cx: &mut Context<Self>,
19403 ) {
19404 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19405 s.move_with(|_, sel| {
19406 if sel.start != sel.end {
19407 sel.reversed = !sel.reversed
19408 }
19409 });
19410 });
19411 self.request_autoscroll(Autoscroll::newest(), cx);
19412 cx.notify();
19413 }
19414
19415 pub fn toggle_focus(
19416 workspace: &mut Workspace,
19417 _: &actions::ToggleFocus,
19418 window: &mut Window,
19419 cx: &mut Context<Workspace>,
19420 ) {
19421 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19422 return;
19423 };
19424 workspace.activate_item(&item, true, true, window, cx);
19425 }
19426
19427 pub fn toggle_fold(
19428 &mut self,
19429 _: &actions::ToggleFold,
19430 window: &mut Window,
19431 cx: &mut Context<Self>,
19432 ) {
19433 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19434 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19435 let selection = self.selections.newest::<Point>(&display_map);
19436
19437 let range = if selection.is_empty() {
19438 let point = selection.head().to_display_point(&display_map);
19439 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19440 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19441 .to_point(&display_map);
19442 start..end
19443 } else {
19444 selection.range()
19445 };
19446 if display_map.folds_in_range(range).next().is_some() {
19447 self.unfold_lines(&Default::default(), window, cx)
19448 } else {
19449 self.fold(&Default::default(), window, cx)
19450 }
19451 } else {
19452 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19453 let buffer_ids: HashSet<_> = self
19454 .selections
19455 .disjoint_anchor_ranges()
19456 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19457 .collect();
19458
19459 let should_unfold = buffer_ids
19460 .iter()
19461 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19462
19463 for buffer_id in buffer_ids {
19464 if should_unfold {
19465 self.unfold_buffer(buffer_id, cx);
19466 } else {
19467 self.fold_buffer(buffer_id, cx);
19468 }
19469 }
19470 }
19471 }
19472
19473 pub fn toggle_fold_recursive(
19474 &mut self,
19475 _: &actions::ToggleFoldRecursive,
19476 window: &mut Window,
19477 cx: &mut Context<Self>,
19478 ) {
19479 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19480
19481 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19482 let range = if selection.is_empty() {
19483 let point = selection.head().to_display_point(&display_map);
19484 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19485 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19486 .to_point(&display_map);
19487 start..end
19488 } else {
19489 selection.range()
19490 };
19491 if display_map.folds_in_range(range).next().is_some() {
19492 self.unfold_recursive(&Default::default(), window, cx)
19493 } else {
19494 self.fold_recursive(&Default::default(), window, cx)
19495 }
19496 }
19497
19498 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19499 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19500 let mut to_fold = Vec::new();
19501 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19502 let selections = self.selections.all_adjusted(&display_map);
19503
19504 for selection in selections {
19505 let range = selection.range().sorted();
19506 let buffer_start_row = range.start.row;
19507
19508 if range.start.row != range.end.row {
19509 let mut found = false;
19510 let mut row = range.start.row;
19511 while row <= range.end.row {
19512 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19513 {
19514 found = true;
19515 row = crease.range().end.row + 1;
19516 to_fold.push(crease);
19517 } else {
19518 row += 1
19519 }
19520 }
19521 if found {
19522 continue;
19523 }
19524 }
19525
19526 for row in (0..=range.start.row).rev() {
19527 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19528 && crease.range().end.row >= buffer_start_row
19529 {
19530 to_fold.push(crease);
19531 if row <= range.start.row {
19532 break;
19533 }
19534 }
19535 }
19536 }
19537
19538 self.fold_creases(to_fold, true, window, cx);
19539 } else {
19540 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19541 let buffer_ids = self
19542 .selections
19543 .disjoint_anchor_ranges()
19544 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19545 .collect::<HashSet<_>>();
19546 for buffer_id in buffer_ids {
19547 self.fold_buffer(buffer_id, cx);
19548 }
19549 }
19550 }
19551
19552 pub fn toggle_fold_all(
19553 &mut self,
19554 _: &actions::ToggleFoldAll,
19555 window: &mut Window,
19556 cx: &mut Context<Self>,
19557 ) {
19558 let has_folds = if self.buffer.read(cx).is_singleton() {
19559 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19560 let has_folds = display_map
19561 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19562 .next()
19563 .is_some();
19564 has_folds
19565 } else {
19566 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19567 let has_folds = buffer_ids
19568 .iter()
19569 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19570 has_folds
19571 };
19572
19573 if has_folds {
19574 self.unfold_all(&actions::UnfoldAll, window, cx);
19575 } else {
19576 self.fold_all(&actions::FoldAll, window, cx);
19577 }
19578 }
19579
19580 fn fold_at_level(
19581 &mut self,
19582 fold_at: &FoldAtLevel,
19583 window: &mut Window,
19584 cx: &mut Context<Self>,
19585 ) {
19586 if !self.buffer.read(cx).is_singleton() {
19587 return;
19588 }
19589
19590 let fold_at_level = fold_at.0;
19591 let snapshot = self.buffer.read(cx).snapshot(cx);
19592 let mut to_fold = Vec::new();
19593 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19594
19595 let row_ranges_to_keep: Vec<Range<u32>> = self
19596 .selections
19597 .all::<Point>(&self.display_snapshot(cx))
19598 .into_iter()
19599 .map(|sel| sel.start.row..sel.end.row)
19600 .collect();
19601
19602 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19603 while start_row < end_row {
19604 match self
19605 .snapshot(window, cx)
19606 .crease_for_buffer_row(MultiBufferRow(start_row))
19607 {
19608 Some(crease) => {
19609 let nested_start_row = crease.range().start.row + 1;
19610 let nested_end_row = crease.range().end.row;
19611
19612 if current_level < fold_at_level {
19613 stack.push((nested_start_row, nested_end_row, current_level + 1));
19614 } else if current_level == fold_at_level {
19615 // Fold iff there is no selection completely contained within the fold region
19616 if !row_ranges_to_keep.iter().any(|selection| {
19617 selection.end >= nested_start_row
19618 && selection.start <= nested_end_row
19619 }) {
19620 to_fold.push(crease);
19621 }
19622 }
19623
19624 start_row = nested_end_row + 1;
19625 }
19626 None => start_row += 1,
19627 }
19628 }
19629 }
19630
19631 self.fold_creases(to_fold, true, window, cx);
19632 }
19633
19634 pub fn fold_at_level_1(
19635 &mut self,
19636 _: &actions::FoldAtLevel1,
19637 window: &mut Window,
19638 cx: &mut Context<Self>,
19639 ) {
19640 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19641 }
19642
19643 pub fn fold_at_level_2(
19644 &mut self,
19645 _: &actions::FoldAtLevel2,
19646 window: &mut Window,
19647 cx: &mut Context<Self>,
19648 ) {
19649 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19650 }
19651
19652 pub fn fold_at_level_3(
19653 &mut self,
19654 _: &actions::FoldAtLevel3,
19655 window: &mut Window,
19656 cx: &mut Context<Self>,
19657 ) {
19658 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19659 }
19660
19661 pub fn fold_at_level_4(
19662 &mut self,
19663 _: &actions::FoldAtLevel4,
19664 window: &mut Window,
19665 cx: &mut Context<Self>,
19666 ) {
19667 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19668 }
19669
19670 pub fn fold_at_level_5(
19671 &mut self,
19672 _: &actions::FoldAtLevel5,
19673 window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19677 }
19678
19679 pub fn fold_at_level_6(
19680 &mut self,
19681 _: &actions::FoldAtLevel6,
19682 window: &mut Window,
19683 cx: &mut Context<Self>,
19684 ) {
19685 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19686 }
19687
19688 pub fn fold_at_level_7(
19689 &mut self,
19690 _: &actions::FoldAtLevel7,
19691 window: &mut Window,
19692 cx: &mut Context<Self>,
19693 ) {
19694 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19695 }
19696
19697 pub fn fold_at_level_8(
19698 &mut self,
19699 _: &actions::FoldAtLevel8,
19700 window: &mut Window,
19701 cx: &mut Context<Self>,
19702 ) {
19703 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19704 }
19705
19706 pub fn fold_at_level_9(
19707 &mut self,
19708 _: &actions::FoldAtLevel9,
19709 window: &mut Window,
19710 cx: &mut Context<Self>,
19711 ) {
19712 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19713 }
19714
19715 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19716 if self.buffer.read(cx).is_singleton() {
19717 let mut fold_ranges = Vec::new();
19718 let snapshot = self.buffer.read(cx).snapshot(cx);
19719
19720 for row in 0..snapshot.max_row().0 {
19721 if let Some(foldable_range) = self
19722 .snapshot(window, cx)
19723 .crease_for_buffer_row(MultiBufferRow(row))
19724 {
19725 fold_ranges.push(foldable_range);
19726 }
19727 }
19728
19729 self.fold_creases(fold_ranges, true, window, cx);
19730 } else {
19731 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19732 editor
19733 .update_in(cx, |editor, _, cx| {
19734 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19735 editor.fold_buffer(buffer_id, cx);
19736 }
19737 })
19738 .ok();
19739 });
19740 }
19741 cx.emit(SearchEvent::ResultsCollapsedChanged(
19742 CollapseDirection::Collapsed,
19743 ));
19744 }
19745
19746 pub fn fold_function_bodies(
19747 &mut self,
19748 _: &actions::FoldFunctionBodies,
19749 window: &mut Window,
19750 cx: &mut Context<Self>,
19751 ) {
19752 let snapshot = self.buffer.read(cx).snapshot(cx);
19753
19754 let ranges = snapshot
19755 .text_object_ranges(
19756 MultiBufferOffset(0)..snapshot.len(),
19757 TreeSitterOptions::default(),
19758 )
19759 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19760 .collect::<Vec<_>>();
19761
19762 let creases = ranges
19763 .into_iter()
19764 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19765 .collect();
19766
19767 self.fold_creases(creases, true, window, cx);
19768 }
19769
19770 pub fn fold_recursive(
19771 &mut self,
19772 _: &actions::FoldRecursive,
19773 window: &mut Window,
19774 cx: &mut Context<Self>,
19775 ) {
19776 let mut to_fold = Vec::new();
19777 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19778 let selections = self.selections.all_adjusted(&display_map);
19779
19780 for selection in selections {
19781 let range = selection.range().sorted();
19782 let buffer_start_row = range.start.row;
19783
19784 if range.start.row != range.end.row {
19785 let mut found = false;
19786 for row in range.start.row..=range.end.row {
19787 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19788 found = true;
19789 to_fold.push(crease);
19790 }
19791 }
19792 if found {
19793 continue;
19794 }
19795 }
19796
19797 for row in (0..=range.start.row).rev() {
19798 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19799 if crease.range().end.row >= buffer_start_row {
19800 to_fold.push(crease);
19801 } else {
19802 break;
19803 }
19804 }
19805 }
19806 }
19807
19808 self.fold_creases(to_fold, true, window, cx);
19809 }
19810
19811 pub fn fold_at(
19812 &mut self,
19813 buffer_row: MultiBufferRow,
19814 window: &mut Window,
19815 cx: &mut Context<Self>,
19816 ) {
19817 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19818
19819 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19820 let autoscroll = self
19821 .selections
19822 .all::<Point>(&display_map)
19823 .iter()
19824 .any(|selection| crease.range().overlaps(&selection.range()));
19825
19826 self.fold_creases(vec![crease], autoscroll, window, cx);
19827 }
19828 }
19829
19830 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19831 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19832 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19833 let buffer = display_map.buffer_snapshot();
19834 let selections = self.selections.all::<Point>(&display_map);
19835 let ranges = selections
19836 .iter()
19837 .map(|s| {
19838 let range = s.display_range(&display_map).sorted();
19839 let mut start = range.start.to_point(&display_map);
19840 let mut end = range.end.to_point(&display_map);
19841 start.column = 0;
19842 end.column = buffer.line_len(MultiBufferRow(end.row));
19843 start..end
19844 })
19845 .collect::<Vec<_>>();
19846
19847 self.unfold_ranges(&ranges, true, true, cx);
19848 } else {
19849 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19850 let buffer_ids = self
19851 .selections
19852 .disjoint_anchor_ranges()
19853 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19854 .collect::<HashSet<_>>();
19855 for buffer_id in buffer_ids {
19856 self.unfold_buffer(buffer_id, cx);
19857 }
19858 }
19859 }
19860
19861 pub fn unfold_recursive(
19862 &mut self,
19863 _: &UnfoldRecursive,
19864 _window: &mut Window,
19865 cx: &mut Context<Self>,
19866 ) {
19867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19868 let selections = self.selections.all::<Point>(&display_map);
19869 let ranges = selections
19870 .iter()
19871 .map(|s| {
19872 let mut range = s.display_range(&display_map).sorted();
19873 *range.start.column_mut() = 0;
19874 *range.end.column_mut() = display_map.line_len(range.end.row());
19875 let start = range.start.to_point(&display_map);
19876 let end = range.end.to_point(&display_map);
19877 start..end
19878 })
19879 .collect::<Vec<_>>();
19880
19881 self.unfold_ranges(&ranges, true, true, cx);
19882 }
19883
19884 pub fn unfold_at(
19885 &mut self,
19886 buffer_row: MultiBufferRow,
19887 _window: &mut Window,
19888 cx: &mut Context<Self>,
19889 ) {
19890 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19891
19892 let intersection_range = Point::new(buffer_row.0, 0)
19893 ..Point::new(
19894 buffer_row.0,
19895 display_map.buffer_snapshot().line_len(buffer_row),
19896 );
19897
19898 let autoscroll = self
19899 .selections
19900 .all::<Point>(&display_map)
19901 .iter()
19902 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19903
19904 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19905 }
19906
19907 pub fn unfold_all(
19908 &mut self,
19909 _: &actions::UnfoldAll,
19910 _window: &mut Window,
19911 cx: &mut Context<Self>,
19912 ) {
19913 if self.buffer.read(cx).is_singleton() {
19914 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19915 self.unfold_ranges(
19916 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19917 true,
19918 true,
19919 cx,
19920 );
19921 } else {
19922 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19923 editor
19924 .update(cx, |editor, cx| {
19925 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19926 editor.unfold_buffer(buffer_id, cx);
19927 }
19928 })
19929 .ok();
19930 });
19931 }
19932 cx.emit(SearchEvent::ResultsCollapsedChanged(
19933 CollapseDirection::Expanded,
19934 ));
19935 }
19936
19937 pub fn fold_selected_ranges(
19938 &mut self,
19939 _: &FoldSelectedRanges,
19940 window: &mut Window,
19941 cx: &mut Context<Self>,
19942 ) {
19943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19944 let selections = self.selections.all_adjusted(&display_map);
19945 let ranges = selections
19946 .into_iter()
19947 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19948 .collect::<Vec<_>>();
19949 self.fold_creases(ranges, true, window, cx);
19950 }
19951
19952 pub fn fold_ranges<T: ToOffset + Clone>(
19953 &mut self,
19954 ranges: Vec<Range<T>>,
19955 auto_scroll: bool,
19956 window: &mut Window,
19957 cx: &mut Context<Self>,
19958 ) {
19959 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19960 let ranges = ranges
19961 .into_iter()
19962 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19963 .collect::<Vec<_>>();
19964 self.fold_creases(ranges, auto_scroll, window, cx);
19965 }
19966
19967 pub fn fold_creases<T: ToOffset + Clone>(
19968 &mut self,
19969 creases: Vec<Crease<T>>,
19970 auto_scroll: bool,
19971 _window: &mut Window,
19972 cx: &mut Context<Self>,
19973 ) {
19974 if creases.is_empty() {
19975 return;
19976 }
19977
19978 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19979
19980 if auto_scroll {
19981 self.request_autoscroll(Autoscroll::fit(), cx);
19982 }
19983
19984 cx.notify();
19985
19986 self.scrollbar_marker_state.dirty = true;
19987 self.folds_did_change(cx);
19988 }
19989
19990 /// Removes any folds whose ranges intersect any of the given ranges.
19991 pub fn unfold_ranges<T: ToOffset + Clone>(
19992 &mut self,
19993 ranges: &[Range<T>],
19994 inclusive: bool,
19995 auto_scroll: bool,
19996 cx: &mut Context<Self>,
19997 ) {
19998 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19999 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
20000 });
20001 self.folds_did_change(cx);
20002 }
20003
20004 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20005 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
20006 return;
20007 }
20008
20009 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20010 self.display_map.update(cx, |display_map, cx| {
20011 display_map.fold_buffers([buffer_id], cx)
20012 });
20013
20014 let snapshot = self.display_snapshot(cx);
20015 self.selections.change_with(&snapshot, |selections| {
20016 selections.remove_selections_from_buffer(buffer_id);
20017 });
20018
20019 cx.emit(EditorEvent::BufferFoldToggled {
20020 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
20021 folded: true,
20022 });
20023 cx.notify();
20024 }
20025
20026 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20027 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20028 return;
20029 }
20030 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20031 self.display_map.update(cx, |display_map, cx| {
20032 display_map.unfold_buffers([buffer_id], cx);
20033 });
20034 cx.emit(EditorEvent::BufferFoldToggled {
20035 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20036 folded: false,
20037 });
20038 cx.notify();
20039 }
20040
20041 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20042 self.display_map.read(cx).is_buffer_folded(buffer)
20043 }
20044
20045 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20046 self.display_map.read(cx).folded_buffers()
20047 }
20048
20049 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20050 self.display_map.update(cx, |display_map, cx| {
20051 display_map.disable_header_for_buffer(buffer_id, cx);
20052 });
20053 cx.notify();
20054 }
20055
20056 /// Removes any folds with the given ranges.
20057 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20058 &mut self,
20059 ranges: &[Range<T>],
20060 type_id: TypeId,
20061 auto_scroll: bool,
20062 cx: &mut Context<Self>,
20063 ) {
20064 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20065 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20066 });
20067 self.folds_did_change(cx);
20068 }
20069
20070 fn remove_folds_with<T: ToOffset + Clone>(
20071 &mut self,
20072 ranges: &[Range<T>],
20073 auto_scroll: bool,
20074 cx: &mut Context<Self>,
20075 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20076 ) {
20077 if ranges.is_empty() {
20078 return;
20079 }
20080
20081 let mut buffers_affected = HashSet::default();
20082 let multi_buffer = self.buffer().read(cx);
20083 for range in ranges {
20084 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20085 buffers_affected.insert(buffer.read(cx).remote_id());
20086 };
20087 }
20088
20089 self.display_map.update(cx, update);
20090
20091 if auto_scroll {
20092 self.request_autoscroll(Autoscroll::fit(), cx);
20093 }
20094
20095 cx.notify();
20096 self.scrollbar_marker_state.dirty = true;
20097 self.active_indent_guides_state.dirty = true;
20098 }
20099
20100 pub fn update_renderer_widths(
20101 &mut self,
20102 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20103 cx: &mut Context<Self>,
20104 ) -> bool {
20105 self.display_map
20106 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20107 }
20108
20109 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20110 self.display_map.read(cx).fold_placeholder.clone()
20111 }
20112
20113 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20114 self.buffer.update(cx, |buffer, cx| {
20115 buffer.set_all_diff_hunks_expanded(cx);
20116 });
20117 }
20118
20119 pub fn expand_all_diff_hunks(
20120 &mut self,
20121 _: &ExpandAllDiffHunks,
20122 _window: &mut Window,
20123 cx: &mut Context<Self>,
20124 ) {
20125 self.buffer.update(cx, |buffer, cx| {
20126 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20127 });
20128 }
20129
20130 pub fn collapse_all_diff_hunks(
20131 &mut self,
20132 _: &CollapseAllDiffHunks,
20133 _window: &mut Window,
20134 cx: &mut Context<Self>,
20135 ) {
20136 self.buffer.update(cx, |buffer, cx| {
20137 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20138 });
20139 }
20140
20141 pub fn toggle_selected_diff_hunks(
20142 &mut self,
20143 _: &ToggleSelectedDiffHunks,
20144 _window: &mut Window,
20145 cx: &mut Context<Self>,
20146 ) {
20147 let ranges: Vec<_> = self
20148 .selections
20149 .disjoint_anchors()
20150 .iter()
20151 .map(|s| s.range())
20152 .collect();
20153 self.toggle_diff_hunks_in_ranges(ranges, cx);
20154 }
20155
20156 pub fn diff_hunks_in_ranges<'a>(
20157 &'a self,
20158 ranges: &'a [Range<Anchor>],
20159 buffer: &'a MultiBufferSnapshot,
20160 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20161 ranges.iter().flat_map(move |range| {
20162 let end_excerpt_id = range.end.excerpt_id;
20163 let range = range.to_point(buffer);
20164 let mut peek_end = range.end;
20165 if range.end.row < buffer.max_row().0 {
20166 peek_end = Point::new(range.end.row + 1, 0);
20167 }
20168 buffer
20169 .diff_hunks_in_range(range.start..peek_end)
20170 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20171 })
20172 }
20173
20174 pub fn has_stageable_diff_hunks_in_ranges(
20175 &self,
20176 ranges: &[Range<Anchor>],
20177 snapshot: &MultiBufferSnapshot,
20178 ) -> bool {
20179 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20180 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20181 }
20182
20183 pub fn toggle_staged_selected_diff_hunks(
20184 &mut self,
20185 _: &::git::ToggleStaged,
20186 _: &mut Window,
20187 cx: &mut Context<Self>,
20188 ) {
20189 let snapshot = self.buffer.read(cx).snapshot(cx);
20190 let ranges: Vec<_> = self
20191 .selections
20192 .disjoint_anchors()
20193 .iter()
20194 .map(|s| s.range())
20195 .collect();
20196 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20197 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20198 }
20199
20200 pub fn set_render_diff_hunk_controls(
20201 &mut self,
20202 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20203 cx: &mut Context<Self>,
20204 ) {
20205 self.render_diff_hunk_controls = render_diff_hunk_controls;
20206 cx.notify();
20207 }
20208
20209 pub fn stage_and_next(
20210 &mut self,
20211 _: &::git::StageAndNext,
20212 window: &mut Window,
20213 cx: &mut Context<Self>,
20214 ) {
20215 self.do_stage_or_unstage_and_next(true, window, cx);
20216 }
20217
20218 pub fn unstage_and_next(
20219 &mut self,
20220 _: &::git::UnstageAndNext,
20221 window: &mut Window,
20222 cx: &mut Context<Self>,
20223 ) {
20224 self.do_stage_or_unstage_and_next(false, window, cx);
20225 }
20226
20227 pub fn stage_or_unstage_diff_hunks(
20228 &mut self,
20229 stage: bool,
20230 ranges: Vec<Range<Anchor>>,
20231 cx: &mut Context<Self>,
20232 ) {
20233 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20234 cx.spawn(async move |this, cx| {
20235 task.await?;
20236 this.update(cx, |this, cx| {
20237 let snapshot = this.buffer.read(cx).snapshot(cx);
20238 let chunk_by = this
20239 .diff_hunks_in_ranges(&ranges, &snapshot)
20240 .chunk_by(|hunk| hunk.buffer_id);
20241 for (buffer_id, hunks) in &chunk_by {
20242 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20243 }
20244 })
20245 })
20246 .detach_and_log_err(cx);
20247 }
20248
20249 fn save_buffers_for_ranges_if_needed(
20250 &mut self,
20251 ranges: &[Range<Anchor>],
20252 cx: &mut Context<Editor>,
20253 ) -> Task<Result<()>> {
20254 let multibuffer = self.buffer.read(cx);
20255 let snapshot = multibuffer.read(cx);
20256 let buffer_ids: HashSet<_> = ranges
20257 .iter()
20258 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20259 .collect();
20260 drop(snapshot);
20261
20262 let mut buffers = HashSet::default();
20263 for buffer_id in buffer_ids {
20264 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20265 let buffer = buffer_entity.read(cx);
20266 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20267 {
20268 buffers.insert(buffer_entity);
20269 }
20270 }
20271 }
20272
20273 if let Some(project) = &self.project {
20274 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20275 } else {
20276 Task::ready(Ok(()))
20277 }
20278 }
20279
20280 fn do_stage_or_unstage_and_next(
20281 &mut self,
20282 stage: bool,
20283 window: &mut Window,
20284 cx: &mut Context<Self>,
20285 ) {
20286 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20287
20288 if ranges.iter().any(|range| range.start != range.end) {
20289 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20290 return;
20291 }
20292
20293 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20294 let snapshot = self.snapshot(window, cx);
20295 let position = self
20296 .selections
20297 .newest::<Point>(&snapshot.display_snapshot)
20298 .head();
20299 let mut row = snapshot
20300 .buffer_snapshot()
20301 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20302 .find(|hunk| hunk.row_range.start.0 > position.row)
20303 .map(|hunk| hunk.row_range.start);
20304
20305 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20306 // Outside of the project diff editor, wrap around to the beginning.
20307 if !all_diff_hunks_expanded {
20308 row = row.or_else(|| {
20309 snapshot
20310 .buffer_snapshot()
20311 .diff_hunks_in_range(Point::zero()..position)
20312 .find(|hunk| hunk.row_range.end.0 < position.row)
20313 .map(|hunk| hunk.row_range.start)
20314 });
20315 }
20316
20317 if let Some(row) = row {
20318 let destination = Point::new(row.0, 0);
20319 let autoscroll = Autoscroll::center();
20320
20321 self.unfold_ranges(&[destination..destination], false, false, cx);
20322 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20323 s.select_ranges([destination..destination]);
20324 });
20325 }
20326 }
20327
20328 fn do_stage_or_unstage(
20329 &self,
20330 stage: bool,
20331 buffer_id: BufferId,
20332 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20333 cx: &mut App,
20334 ) -> Option<()> {
20335 let project = self.project()?;
20336 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20337 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20338 let buffer_snapshot = buffer.read(cx).snapshot();
20339 let file_exists = buffer_snapshot
20340 .file()
20341 .is_some_and(|file| file.disk_state().exists());
20342 diff.update(cx, |diff, cx| {
20343 diff.stage_or_unstage_hunks(
20344 stage,
20345 &hunks
20346 .map(|hunk| buffer_diff::DiffHunk {
20347 buffer_range: hunk.buffer_range,
20348 // We don't need to pass in word diffs here because they're only used for rendering and
20349 // this function changes internal state
20350 base_word_diffs: Vec::default(),
20351 buffer_word_diffs: Vec::default(),
20352 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20353 ..hunk.diff_base_byte_range.end.0,
20354 secondary_status: hunk.status.secondary,
20355 range: Point::zero()..Point::zero(), // unused
20356 })
20357 .collect::<Vec<_>>(),
20358 &buffer_snapshot,
20359 file_exists,
20360 cx,
20361 )
20362 });
20363 None
20364 }
20365
20366 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20367 let ranges: Vec<_> = self
20368 .selections
20369 .disjoint_anchors()
20370 .iter()
20371 .map(|s| s.range())
20372 .collect();
20373 self.buffer
20374 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20375 }
20376
20377 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20378 self.buffer.update(cx, |buffer, cx| {
20379 let ranges = vec![Anchor::min()..Anchor::max()];
20380 if !buffer.all_diff_hunks_expanded()
20381 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20382 {
20383 buffer.collapse_diff_hunks(ranges, cx);
20384 true
20385 } else {
20386 false
20387 }
20388 })
20389 }
20390
20391 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20392 if self.buffer.read(cx).all_diff_hunks_expanded() {
20393 return true;
20394 }
20395 let ranges = vec![Anchor::min()..Anchor::max()];
20396 self.buffer
20397 .read(cx)
20398 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20399 }
20400
20401 fn toggle_diff_hunks_in_ranges(
20402 &mut self,
20403 ranges: Vec<Range<Anchor>>,
20404 cx: &mut Context<Editor>,
20405 ) {
20406 self.buffer.update(cx, |buffer, cx| {
20407 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20408 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20409 })
20410 }
20411
20412 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20413 self.buffer.update(cx, |buffer, cx| {
20414 let snapshot = buffer.snapshot(cx);
20415 let excerpt_id = range.end.excerpt_id;
20416 let point_range = range.to_point(&snapshot);
20417 let expand = !buffer.single_hunk_is_expanded(range, cx);
20418 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20419 })
20420 }
20421
20422 pub(crate) fn apply_all_diff_hunks(
20423 &mut self,
20424 _: &ApplyAllDiffHunks,
20425 window: &mut Window,
20426 cx: &mut Context<Self>,
20427 ) {
20428 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20429
20430 let buffers = self.buffer.read(cx).all_buffers();
20431 for branch_buffer in buffers {
20432 branch_buffer.update(cx, |branch_buffer, cx| {
20433 branch_buffer.merge_into_base(Vec::new(), cx);
20434 });
20435 }
20436
20437 if let Some(project) = self.project.clone() {
20438 self.save(
20439 SaveOptions {
20440 format: true,
20441 autosave: false,
20442 },
20443 project,
20444 window,
20445 cx,
20446 )
20447 .detach_and_log_err(cx);
20448 }
20449 }
20450
20451 pub(crate) fn apply_selected_diff_hunks(
20452 &mut self,
20453 _: &ApplyDiffHunk,
20454 window: &mut Window,
20455 cx: &mut Context<Self>,
20456 ) {
20457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20458 let snapshot = self.snapshot(window, cx);
20459 let hunks = snapshot.hunks_for_ranges(
20460 self.selections
20461 .all(&snapshot.display_snapshot)
20462 .into_iter()
20463 .map(|selection| selection.range()),
20464 );
20465 let mut ranges_by_buffer = HashMap::default();
20466 self.transact(window, cx, |editor, _window, cx| {
20467 for hunk in hunks {
20468 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20469 ranges_by_buffer
20470 .entry(buffer.clone())
20471 .or_insert_with(Vec::new)
20472 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20473 }
20474 }
20475
20476 for (buffer, ranges) in ranges_by_buffer {
20477 buffer.update(cx, |buffer, cx| {
20478 buffer.merge_into_base(ranges, cx);
20479 });
20480 }
20481 });
20482
20483 if let Some(project) = self.project.clone() {
20484 self.save(
20485 SaveOptions {
20486 format: true,
20487 autosave: false,
20488 },
20489 project,
20490 window,
20491 cx,
20492 )
20493 .detach_and_log_err(cx);
20494 }
20495 }
20496
20497 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20498 if hovered != self.gutter_hovered {
20499 self.gutter_hovered = hovered;
20500 cx.notify();
20501 }
20502 }
20503
20504 pub fn insert_blocks(
20505 &mut self,
20506 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20507 autoscroll: Option<Autoscroll>,
20508 cx: &mut Context<Self>,
20509 ) -> Vec<CustomBlockId> {
20510 let blocks = self
20511 .display_map
20512 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20513 if let Some(autoscroll) = autoscroll {
20514 self.request_autoscroll(autoscroll, cx);
20515 }
20516 cx.notify();
20517 blocks
20518 }
20519
20520 pub fn resize_blocks(
20521 &mut self,
20522 heights: HashMap<CustomBlockId, u32>,
20523 autoscroll: Option<Autoscroll>,
20524 cx: &mut Context<Self>,
20525 ) {
20526 self.display_map
20527 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20528 if let Some(autoscroll) = autoscroll {
20529 self.request_autoscroll(autoscroll, cx);
20530 }
20531 cx.notify();
20532 }
20533
20534 pub fn replace_blocks(
20535 &mut self,
20536 renderers: HashMap<CustomBlockId, RenderBlock>,
20537 autoscroll: Option<Autoscroll>,
20538 cx: &mut Context<Self>,
20539 ) {
20540 self.display_map
20541 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20542 if let Some(autoscroll) = autoscroll {
20543 self.request_autoscroll(autoscroll, cx);
20544 }
20545 cx.notify();
20546 }
20547
20548 pub fn remove_blocks(
20549 &mut self,
20550 block_ids: HashSet<CustomBlockId>,
20551 autoscroll: Option<Autoscroll>,
20552 cx: &mut Context<Self>,
20553 ) {
20554 self.display_map.update(cx, |display_map, cx| {
20555 display_map.remove_blocks(block_ids, cx)
20556 });
20557 if let Some(autoscroll) = autoscroll {
20558 self.request_autoscroll(autoscroll, cx);
20559 }
20560 cx.notify();
20561 }
20562
20563 pub fn row_for_block(
20564 &self,
20565 block_id: CustomBlockId,
20566 cx: &mut Context<Self>,
20567 ) -> Option<DisplayRow> {
20568 self.display_map
20569 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20570 }
20571
20572 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20573 self.focused_block = Some(focused_block);
20574 }
20575
20576 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20577 self.focused_block.take()
20578 }
20579
20580 pub fn insert_creases(
20581 &mut self,
20582 creases: impl IntoIterator<Item = Crease<Anchor>>,
20583 cx: &mut Context<Self>,
20584 ) -> Vec<CreaseId> {
20585 self.display_map
20586 .update(cx, |map, cx| map.insert_creases(creases, cx))
20587 }
20588
20589 pub fn remove_creases(
20590 &mut self,
20591 ids: impl IntoIterator<Item = CreaseId>,
20592 cx: &mut Context<Self>,
20593 ) -> Vec<(CreaseId, Range<Anchor>)> {
20594 self.display_map
20595 .update(cx, |map, cx| map.remove_creases(ids, cx))
20596 }
20597
20598 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20599 self.display_map
20600 .update(cx, |map, cx| map.snapshot(cx))
20601 .longest_row()
20602 }
20603
20604 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20605 self.display_map
20606 .update(cx, |map, cx| map.snapshot(cx))
20607 .max_point()
20608 }
20609
20610 pub fn text(&self, cx: &App) -> String {
20611 self.buffer.read(cx).read(cx).text()
20612 }
20613
20614 pub fn is_empty(&self, cx: &App) -> bool {
20615 self.buffer.read(cx).read(cx).is_empty()
20616 }
20617
20618 pub fn text_option(&self, cx: &App) -> Option<String> {
20619 let text = self.text(cx);
20620 let text = text.trim();
20621
20622 if text.is_empty() {
20623 return None;
20624 }
20625
20626 Some(text.to_string())
20627 }
20628
20629 pub fn set_text(
20630 &mut self,
20631 text: impl Into<Arc<str>>,
20632 window: &mut Window,
20633 cx: &mut Context<Self>,
20634 ) {
20635 self.transact(window, cx, |this, _, cx| {
20636 this.buffer
20637 .read(cx)
20638 .as_singleton()
20639 .expect("you can only call set_text on editors for singleton buffers")
20640 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20641 });
20642 }
20643
20644 pub fn display_text(&self, cx: &mut App) -> String {
20645 self.display_map
20646 .update(cx, |map, cx| map.snapshot(cx))
20647 .text()
20648 }
20649
20650 fn create_minimap(
20651 &self,
20652 minimap_settings: MinimapSettings,
20653 window: &mut Window,
20654 cx: &mut Context<Self>,
20655 ) -> Option<Entity<Self>> {
20656 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20657 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20658 }
20659
20660 fn initialize_new_minimap(
20661 &self,
20662 minimap_settings: MinimapSettings,
20663 window: &mut Window,
20664 cx: &mut Context<Self>,
20665 ) -> Entity<Self> {
20666 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20667 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20668
20669 let mut minimap = Editor::new_internal(
20670 EditorMode::Minimap {
20671 parent: cx.weak_entity(),
20672 },
20673 self.buffer.clone(),
20674 None,
20675 Some(self.display_map.clone()),
20676 window,
20677 cx,
20678 );
20679 minimap.scroll_manager.clone_state(&self.scroll_manager);
20680 minimap.set_text_style_refinement(TextStyleRefinement {
20681 font_size: Some(MINIMAP_FONT_SIZE),
20682 font_weight: Some(MINIMAP_FONT_WEIGHT),
20683 font_family: Some(MINIMAP_FONT_FAMILY),
20684 ..Default::default()
20685 });
20686 minimap.update_minimap_configuration(minimap_settings, cx);
20687 cx.new(|_| minimap)
20688 }
20689
20690 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20691 let current_line_highlight = minimap_settings
20692 .current_line_highlight
20693 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20694 self.set_current_line_highlight(Some(current_line_highlight));
20695 }
20696
20697 pub fn minimap(&self) -> Option<&Entity<Self>> {
20698 self.minimap
20699 .as_ref()
20700 .filter(|_| self.minimap_visibility.visible())
20701 }
20702
20703 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20704 let mut wrap_guides = smallvec![];
20705
20706 if self.show_wrap_guides == Some(false) {
20707 return wrap_guides;
20708 }
20709
20710 let settings = self.buffer.read(cx).language_settings(cx);
20711 if settings.show_wrap_guides {
20712 match self.soft_wrap_mode(cx) {
20713 SoftWrap::Column(soft_wrap) => {
20714 wrap_guides.push((soft_wrap as usize, true));
20715 }
20716 SoftWrap::Bounded(soft_wrap) => {
20717 wrap_guides.push((soft_wrap as usize, true));
20718 }
20719 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20720 }
20721 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20722 }
20723
20724 wrap_guides
20725 }
20726
20727 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20728 let settings = self.buffer.read(cx).language_settings(cx);
20729 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20730 match mode {
20731 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20732 SoftWrap::None
20733 }
20734 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20735 language_settings::SoftWrap::PreferredLineLength => {
20736 SoftWrap::Column(settings.preferred_line_length)
20737 }
20738 language_settings::SoftWrap::Bounded => {
20739 SoftWrap::Bounded(settings.preferred_line_length)
20740 }
20741 }
20742 }
20743
20744 pub fn set_soft_wrap_mode(
20745 &mut self,
20746 mode: language_settings::SoftWrap,
20747 cx: &mut Context<Self>,
20748 ) {
20749 self.soft_wrap_mode_override = Some(mode);
20750 cx.notify();
20751 }
20752
20753 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20754 self.hard_wrap = hard_wrap;
20755 cx.notify();
20756 }
20757
20758 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20759 self.text_style_refinement = Some(style);
20760 }
20761
20762 /// called by the Element so we know what style we were most recently rendered with.
20763 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20764 // We intentionally do not inform the display map about the minimap style
20765 // so that wrapping is not recalculated and stays consistent for the editor
20766 // and its linked minimap.
20767 if !self.mode.is_minimap() {
20768 let font = style.text.font();
20769 let font_size = style.text.font_size.to_pixels(window.rem_size());
20770 let display_map = self
20771 .placeholder_display_map
20772 .as_ref()
20773 .filter(|_| self.is_empty(cx))
20774 .unwrap_or(&self.display_map);
20775
20776 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20777 }
20778 self.style = Some(style);
20779 }
20780
20781 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20782 if self.style.is_none() {
20783 self.style = Some(self.create_style(cx));
20784 }
20785 self.style.as_ref().unwrap()
20786 }
20787
20788 // Called by the element. This method is not designed to be called outside of the editor
20789 // element's layout code because it does not notify when rewrapping is computed synchronously.
20790 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20791 if self.is_empty(cx) {
20792 self.placeholder_display_map
20793 .as_ref()
20794 .map_or(false, |display_map| {
20795 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20796 })
20797 } else {
20798 self.display_map
20799 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20800 }
20801 }
20802
20803 pub fn set_soft_wrap(&mut self) {
20804 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20805 }
20806
20807 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20808 if self.soft_wrap_mode_override.is_some() {
20809 self.soft_wrap_mode_override.take();
20810 } else {
20811 let soft_wrap = match self.soft_wrap_mode(cx) {
20812 SoftWrap::GitDiff => return,
20813 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20814 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20815 language_settings::SoftWrap::None
20816 }
20817 };
20818 self.soft_wrap_mode_override = Some(soft_wrap);
20819 }
20820 cx.notify();
20821 }
20822
20823 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20824 let Some(workspace) = self.workspace() else {
20825 return;
20826 };
20827 let fs = workspace.read(cx).app_state().fs.clone();
20828 let current_show = TabBarSettings::get_global(cx).show;
20829 update_settings_file(fs, cx, move |setting, _| {
20830 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20831 });
20832 }
20833
20834 pub fn toggle_indent_guides(
20835 &mut self,
20836 _: &ToggleIndentGuides,
20837 _: &mut Window,
20838 cx: &mut Context<Self>,
20839 ) {
20840 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20841 self.buffer
20842 .read(cx)
20843 .language_settings(cx)
20844 .indent_guides
20845 .enabled
20846 });
20847 self.show_indent_guides = Some(!currently_enabled);
20848 cx.notify();
20849 }
20850
20851 fn should_show_indent_guides(&self) -> Option<bool> {
20852 self.show_indent_guides
20853 }
20854
20855 pub fn disable_indent_guides_for_buffer(
20856 &mut self,
20857 buffer_id: BufferId,
20858 cx: &mut Context<Self>,
20859 ) {
20860 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20861 cx.notify();
20862 }
20863
20864 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20865 self.buffers_with_disabled_indent_guides
20866 .contains(&buffer_id)
20867 }
20868
20869 pub fn toggle_line_numbers(
20870 &mut self,
20871 _: &ToggleLineNumbers,
20872 _: &mut Window,
20873 cx: &mut Context<Self>,
20874 ) {
20875 let mut editor_settings = EditorSettings::get_global(cx).clone();
20876 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20877 EditorSettings::override_global(editor_settings, cx);
20878 }
20879
20880 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20881 if let Some(show_line_numbers) = self.show_line_numbers {
20882 return show_line_numbers;
20883 }
20884 EditorSettings::get_global(cx).gutter.line_numbers
20885 }
20886
20887 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20888 match (
20889 self.use_relative_line_numbers,
20890 EditorSettings::get_global(cx).relative_line_numbers,
20891 ) {
20892 (None, setting) => setting,
20893 (Some(false), _) => RelativeLineNumbers::Disabled,
20894 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20895 (Some(true), _) => RelativeLineNumbers::Enabled,
20896 }
20897 }
20898
20899 pub fn toggle_relative_line_numbers(
20900 &mut self,
20901 _: &ToggleRelativeLineNumbers,
20902 _: &mut Window,
20903 cx: &mut Context<Self>,
20904 ) {
20905 let is_relative = self.relative_line_numbers(cx);
20906 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20907 }
20908
20909 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20910 self.use_relative_line_numbers = is_relative;
20911 cx.notify();
20912 }
20913
20914 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20915 self.show_gutter = show_gutter;
20916 cx.notify();
20917 }
20918
20919 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20920 self.show_scrollbars = ScrollbarAxes {
20921 horizontal: show,
20922 vertical: show,
20923 };
20924 cx.notify();
20925 }
20926
20927 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20928 self.show_scrollbars.vertical = show;
20929 cx.notify();
20930 }
20931
20932 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20933 self.show_scrollbars.horizontal = show;
20934 cx.notify();
20935 }
20936
20937 pub fn set_minimap_visibility(
20938 &mut self,
20939 minimap_visibility: MinimapVisibility,
20940 window: &mut Window,
20941 cx: &mut Context<Self>,
20942 ) {
20943 if self.minimap_visibility != minimap_visibility {
20944 if minimap_visibility.visible() && self.minimap.is_none() {
20945 let minimap_settings = EditorSettings::get_global(cx).minimap;
20946 self.minimap =
20947 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20948 }
20949 self.minimap_visibility = minimap_visibility;
20950 cx.notify();
20951 }
20952 }
20953
20954 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20955 self.set_show_scrollbars(false, cx);
20956 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20957 }
20958
20959 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20960 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20961 }
20962
20963 /// Normally the text in full mode and auto height editors is padded on the
20964 /// left side by roughly half a character width for improved hit testing.
20965 ///
20966 /// Use this method to disable this for cases where this is not wanted (e.g.
20967 /// if you want to align the editor text with some other text above or below)
20968 /// or if you want to add this padding to single-line editors.
20969 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20970 self.offset_content = offset_content;
20971 cx.notify();
20972 }
20973
20974 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20975 self.show_line_numbers = Some(show_line_numbers);
20976 cx.notify();
20977 }
20978
20979 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20980 self.disable_expand_excerpt_buttons = true;
20981 cx.notify();
20982 }
20983
20984 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
20985 self.delegate_expand_excerpts = delegate;
20986 }
20987
20988 pub fn set_scroll_companion(&mut self, companion: Option<WeakEntity<Editor>>) {
20989 self.scroll_companion = companion;
20990 }
20991
20992 pub fn scroll_companion(&self) -> Option<&WeakEntity<Editor>> {
20993 self.scroll_companion.as_ref()
20994 }
20995
20996 pub fn set_on_local_selections_changed(
20997 &mut self,
20998 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
20999 ) {
21000 self.on_local_selections_changed = callback;
21001 }
21002
21003 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21004 self.suppress_selection_callback = suppress;
21005 }
21006
21007 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21008 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21009 cx.notify();
21010 }
21011
21012 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21013 self.show_code_actions = Some(show_code_actions);
21014 cx.notify();
21015 }
21016
21017 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21018 self.show_runnables = Some(show_runnables);
21019 cx.notify();
21020 }
21021
21022 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21023 self.show_breakpoints = Some(show_breakpoints);
21024 cx.notify();
21025 }
21026
21027 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21028 self.show_diff_review_button = show;
21029 cx.notify();
21030 }
21031
21032 pub fn show_diff_review_button(&self) -> bool {
21033 self.show_diff_review_button
21034 }
21035
21036 pub fn render_diff_review_button(
21037 &self,
21038 display_row: DisplayRow,
21039 width: Pixels,
21040 cx: &mut Context<Self>,
21041 ) -> impl IntoElement {
21042 let text_color = cx.theme().colors().text;
21043 let icon_color = cx.theme().colors().icon_accent;
21044
21045 h_flex()
21046 .id("diff_review_button")
21047 .cursor_pointer()
21048 .w(width - px(1.))
21049 .h(relative(0.9))
21050 .justify_center()
21051 .rounded_sm()
21052 .border_1()
21053 .border_color(text_color.opacity(0.1))
21054 .bg(text_color.opacity(0.15))
21055 .hover(|s| {
21056 s.bg(icon_color.opacity(0.4))
21057 .border_color(icon_color.opacity(0.5))
21058 })
21059 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21060 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21061 .on_mouse_down(
21062 gpui::MouseButton::Left,
21063 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21064 editor.start_diff_review_drag(display_row, window, cx);
21065 }),
21066 )
21067 }
21068
21069 pub fn start_diff_review_drag(
21070 &mut self,
21071 display_row: DisplayRow,
21072 window: &mut Window,
21073 cx: &mut Context<Self>,
21074 ) {
21075 let snapshot = self.snapshot(window, cx);
21076 let point = snapshot
21077 .display_snapshot
21078 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21079 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21080 self.diff_review_drag_state = Some(DiffReviewDragState {
21081 start_anchor: anchor,
21082 current_anchor: anchor,
21083 });
21084 cx.notify();
21085 }
21086
21087 pub fn update_diff_review_drag(
21088 &mut self,
21089 display_row: DisplayRow,
21090 window: &mut Window,
21091 cx: &mut Context<Self>,
21092 ) {
21093 if self.diff_review_drag_state.is_none() {
21094 return;
21095 }
21096 let snapshot = self.snapshot(window, cx);
21097 let point = snapshot
21098 .display_snapshot
21099 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21100 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21101 if let Some(drag_state) = &mut self.diff_review_drag_state {
21102 drag_state.current_anchor = anchor;
21103 cx.notify();
21104 }
21105 }
21106
21107 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21108 if let Some(drag_state) = self.diff_review_drag_state.take() {
21109 let snapshot = self.snapshot(window, cx);
21110 let range = drag_state.row_range(&snapshot.display_snapshot);
21111 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21112 }
21113 cx.notify();
21114 }
21115
21116 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21117 self.diff_review_drag_state = None;
21118 cx.notify();
21119 }
21120
21121 /// Calculates the appropriate block height for the diff review overlay.
21122 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21123 /// and 2 lines per comment when expanded.
21124 fn calculate_overlay_height(
21125 &self,
21126 hunk_key: &DiffHunkKey,
21127 comments_expanded: bool,
21128 snapshot: &MultiBufferSnapshot,
21129 ) -> u32 {
21130 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21131 let base_height: u32 = 2; // Input row with avatar and buttons
21132
21133 if comment_count == 0 {
21134 base_height
21135 } else if comments_expanded {
21136 // Header (1 line) + 2 lines per comment
21137 base_height + 1 + (comment_count as u32 * 2)
21138 } else {
21139 // Just header when collapsed
21140 base_height + 1
21141 }
21142 }
21143
21144 pub fn show_diff_review_overlay(
21145 &mut self,
21146 display_range: Range<DisplayRow>,
21147 window: &mut Window,
21148 cx: &mut Context<Self>,
21149 ) {
21150 let Range { start, end } = display_range.sorted();
21151
21152 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21153 let editor_snapshot = self.snapshot(window, cx);
21154
21155 // Convert display rows to multibuffer points
21156 let start_point = editor_snapshot
21157 .display_snapshot
21158 .display_point_to_point(start.as_display_point(), Bias::Left);
21159 let end_point = editor_snapshot
21160 .display_snapshot
21161 .display_point_to_point(end.as_display_point(), Bias::Left);
21162 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21163
21164 // Create anchor range for the selected lines (start of first line to end of last line)
21165 let line_end = Point::new(
21166 end_point.row,
21167 buffer_snapshot.line_len(end_multi_buffer_row),
21168 );
21169 let anchor_range =
21170 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21171
21172 // Compute the hunk key for this display row
21173 let file_path = buffer_snapshot
21174 .file_at(start_point)
21175 .map(|file: &Arc<dyn language::File>| file.path().clone())
21176 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21177 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21178 let new_hunk_key = DiffHunkKey {
21179 file_path,
21180 hunk_start_anchor,
21181 };
21182
21183 // Check if we already have an overlay for this hunk
21184 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21185 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21186 }) {
21187 // Just focus the existing overlay's prompt editor
21188 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21189 window.focus(&focus_handle, cx);
21190 return;
21191 }
21192
21193 // Dismiss overlays that have no comments for their hunks
21194 self.dismiss_overlays_without_comments(cx);
21195
21196 // Get the current user's avatar URI from the project's user_store
21197 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21198 let user_store = project.read(cx).user_store();
21199 user_store
21200 .read(cx)
21201 .current_user()
21202 .map(|user| user.avatar_uri.clone())
21203 });
21204
21205 // Create anchor at the end of the last row so the block appears immediately below it
21206 // Use multibuffer coordinates for anchor creation
21207 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21208 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21209
21210 // Use the hunk key we already computed
21211 let hunk_key = new_hunk_key;
21212
21213 // Create the prompt editor for the review input
21214 let prompt_editor = cx.new(|cx| {
21215 let mut editor = Editor::single_line(window, cx);
21216 editor.set_placeholder_text("Add a review comment...", window, cx);
21217 editor
21218 });
21219
21220 // Register the Newline action on the prompt editor to submit the review
21221 let parent_editor = cx.entity().downgrade();
21222 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21223 prompt_editor.register_action({
21224 let parent_editor = parent_editor.clone();
21225 move |_: &crate::actions::Newline, window, cx| {
21226 if let Some(editor) = parent_editor.upgrade() {
21227 editor.update(cx, |editor, cx| {
21228 editor.submit_diff_review_comment(window, cx);
21229 });
21230 }
21231 }
21232 })
21233 });
21234
21235 // Calculate initial height based on existing comments for this hunk
21236 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21237
21238 // Create the overlay block
21239 let prompt_editor_for_render = prompt_editor.clone();
21240 let hunk_key_for_render = hunk_key.clone();
21241 let editor_handle = cx.entity().downgrade();
21242 let block = BlockProperties {
21243 style: BlockStyle::Sticky,
21244 placement: BlockPlacement::Below(anchor),
21245 height: Some(initial_height),
21246 render: Arc::new(move |cx| {
21247 Self::render_diff_review_overlay(
21248 &prompt_editor_for_render,
21249 &hunk_key_for_render,
21250 &editor_handle,
21251 cx,
21252 )
21253 }),
21254 priority: 0,
21255 };
21256
21257 let block_ids = self.insert_blocks([block], None, cx);
21258 let Some(block_id) = block_ids.into_iter().next() else {
21259 log::error!("Failed to insert diff review overlay block");
21260 return;
21261 };
21262
21263 self.diff_review_overlays.push(DiffReviewOverlay {
21264 anchor_range,
21265 block_id,
21266 prompt_editor: prompt_editor.clone(),
21267 hunk_key,
21268 comments_expanded: true,
21269 inline_edit_editors: HashMap::default(),
21270 inline_edit_subscriptions: HashMap::default(),
21271 user_avatar_uri,
21272 _subscription: subscription,
21273 });
21274
21275 // Focus the prompt editor
21276 let focus_handle = prompt_editor.focus_handle(cx);
21277 window.focus(&focus_handle, cx);
21278
21279 cx.notify();
21280 }
21281
21282 /// Dismisses all diff review overlays.
21283 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21284 if self.diff_review_overlays.is_empty() {
21285 return;
21286 }
21287 let block_ids: HashSet<_> = self
21288 .diff_review_overlays
21289 .drain(..)
21290 .map(|overlay| overlay.block_id)
21291 .collect();
21292 self.remove_blocks(block_ids, None, cx);
21293 cx.notify();
21294 }
21295
21296 /// Dismisses overlays that have no comments stored for their hunks.
21297 /// Keeps overlays that have at least one comment.
21298 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21299 let snapshot = self.buffer.read(cx).snapshot(cx);
21300
21301 // First, compute which overlays have comments (to avoid borrow issues with retain)
21302 let overlays_with_comments: Vec<bool> = self
21303 .diff_review_overlays
21304 .iter()
21305 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21306 .collect();
21307
21308 // Now collect block IDs to remove and retain overlays
21309 let mut block_ids_to_remove = HashSet::default();
21310 let mut index = 0;
21311 self.diff_review_overlays.retain(|overlay| {
21312 let has_comments = overlays_with_comments[index];
21313 index += 1;
21314 if !has_comments {
21315 block_ids_to_remove.insert(overlay.block_id);
21316 }
21317 has_comments
21318 });
21319
21320 if !block_ids_to_remove.is_empty() {
21321 self.remove_blocks(block_ids_to_remove, None, cx);
21322 cx.notify();
21323 }
21324 }
21325
21326 /// Refreshes the diff review overlay block to update its height and render function.
21327 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21328 fn refresh_diff_review_overlay_height(
21329 &mut self,
21330 hunk_key: &DiffHunkKey,
21331 _window: &mut Window,
21332 cx: &mut Context<Self>,
21333 ) {
21334 // Extract all needed data from overlay first to avoid borrow conflicts
21335 let snapshot = self.buffer.read(cx).snapshot(cx);
21336 let (comments_expanded, block_id, prompt_editor) = {
21337 let Some(overlay) = self
21338 .diff_review_overlays
21339 .iter()
21340 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21341 else {
21342 return;
21343 };
21344
21345 (
21346 overlay.comments_expanded,
21347 overlay.block_id,
21348 overlay.prompt_editor.clone(),
21349 )
21350 };
21351
21352 // Calculate new height
21353 let snapshot = self.buffer.read(cx).snapshot(cx);
21354 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21355
21356 // Update the block height using resize_blocks (avoids flicker)
21357 let mut heights = HashMap::default();
21358 heights.insert(block_id, new_height);
21359 self.resize_blocks(heights, None, cx);
21360
21361 // Update the render function using replace_blocks (avoids flicker)
21362 let hunk_key_for_render = hunk_key.clone();
21363 let editor_handle = cx.entity().downgrade();
21364 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21365 Arc::new(move |cx| {
21366 Self::render_diff_review_overlay(
21367 &prompt_editor,
21368 &hunk_key_for_render,
21369 &editor_handle,
21370 cx,
21371 )
21372 });
21373
21374 let mut renderers = HashMap::default();
21375 renderers.insert(block_id, render);
21376 self.replace_blocks(renderers, None, cx);
21377 }
21378
21379 /// Action handler for SubmitDiffReviewComment.
21380 pub fn submit_diff_review_comment_action(
21381 &mut self,
21382 _: &SubmitDiffReviewComment,
21383 window: &mut Window,
21384 cx: &mut Context<Self>,
21385 ) {
21386 self.submit_diff_review_comment(window, cx);
21387 }
21388
21389 /// Stores the diff review comment locally.
21390 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21391 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21392 // Find the overlay that currently has focus
21393 let overlay_index = self
21394 .diff_review_overlays
21395 .iter()
21396 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21397 let Some(overlay_index) = overlay_index else {
21398 return;
21399 };
21400 let overlay = &self.diff_review_overlays[overlay_index];
21401
21402 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21403 if comment_text.is_empty() {
21404 return;
21405 }
21406
21407 let anchor_range = overlay.anchor_range.clone();
21408 let hunk_key = overlay.hunk_key.clone();
21409
21410 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21411
21412 // Clear the prompt editor but keep the overlay open
21413 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21414 overlay.prompt_editor.update(cx, |editor, cx| {
21415 editor.clear(window, cx);
21416 });
21417 }
21418
21419 // Refresh the overlay to update the block height for the new comment
21420 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21421
21422 cx.notify();
21423 }
21424
21425 /// Returns the prompt editor for the diff review overlay, if one is active.
21426 /// This is primarily used for testing.
21427 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21428 self.diff_review_overlays
21429 .first()
21430 .map(|overlay| &overlay.prompt_editor)
21431 }
21432
21433 /// Returns the line range for the first diff review overlay, if one is active.
21434 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21435 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21436 let overlay = self.diff_review_overlays.first()?;
21437 let snapshot = self.buffer.read(cx).snapshot(cx);
21438 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21439 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21440 let start_row = snapshot
21441 .point_to_buffer_point(start_point)
21442 .map(|(_, p, _)| p.row)
21443 .unwrap_or(start_point.row);
21444 let end_row = snapshot
21445 .point_to_buffer_point(end_point)
21446 .map(|(_, p, _)| p.row)
21447 .unwrap_or(end_point.row);
21448 Some((start_row, end_row))
21449 }
21450
21451 /// Sets whether the comments section is expanded in the diff review overlay.
21452 /// This is primarily used for testing.
21453 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21454 for overlay in &mut self.diff_review_overlays {
21455 overlay.comments_expanded = expanded;
21456 }
21457 cx.notify();
21458 }
21459
21460 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21461 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21462 a.file_path == b.file_path
21463 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21464 }
21465
21466 /// Returns comments for a specific hunk, ordered by creation time.
21467 pub fn comments_for_hunk<'a>(
21468 &'a self,
21469 key: &DiffHunkKey,
21470 snapshot: &MultiBufferSnapshot,
21471 ) -> &'a [StoredReviewComment] {
21472 let key_point = key.hunk_start_anchor.to_point(snapshot);
21473 self.stored_review_comments
21474 .iter()
21475 .find(|(k, _)| {
21476 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21477 })
21478 .map(|(_, comments)| comments.as_slice())
21479 .unwrap_or(&[])
21480 }
21481
21482 /// Returns the total count of stored review comments across all hunks.
21483 pub fn total_review_comment_count(&self) -> usize {
21484 self.stored_review_comments
21485 .iter()
21486 .map(|(_, v)| v.len())
21487 .sum()
21488 }
21489
21490 /// Returns the count of comments for a specific hunk.
21491 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21492 let key_point = key.hunk_start_anchor.to_point(snapshot);
21493 self.stored_review_comments
21494 .iter()
21495 .find(|(k, _)| {
21496 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21497 })
21498 .map(|(_, v)| v.len())
21499 .unwrap_or(0)
21500 }
21501
21502 /// Adds a new review comment to a specific hunk.
21503 pub fn add_review_comment(
21504 &mut self,
21505 hunk_key: DiffHunkKey,
21506 comment: String,
21507 anchor_range: Range<Anchor>,
21508 cx: &mut Context<Self>,
21509 ) -> usize {
21510 let id = self.next_review_comment_id;
21511 self.next_review_comment_id += 1;
21512
21513 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21514
21515 let snapshot = self.buffer.read(cx).snapshot(cx);
21516 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21517
21518 // Find existing entry for this hunk or add a new one
21519 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21520 k.file_path == hunk_key.file_path
21521 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21522 }) {
21523 comments.push(stored_comment);
21524 } else {
21525 self.stored_review_comments
21526 .push((hunk_key, vec![stored_comment]));
21527 }
21528
21529 cx.emit(EditorEvent::ReviewCommentsChanged {
21530 total_count: self.total_review_comment_count(),
21531 });
21532 cx.notify();
21533 id
21534 }
21535
21536 /// Removes a review comment by ID from any hunk.
21537 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21538 for (_, comments) in self.stored_review_comments.iter_mut() {
21539 if let Some(index) = comments.iter().position(|c| c.id == id) {
21540 comments.remove(index);
21541 cx.emit(EditorEvent::ReviewCommentsChanged {
21542 total_count: self.total_review_comment_count(),
21543 });
21544 cx.notify();
21545 return true;
21546 }
21547 }
21548 false
21549 }
21550
21551 /// Updates a review comment's text by ID.
21552 pub fn update_review_comment(
21553 &mut self,
21554 id: usize,
21555 new_comment: String,
21556 cx: &mut Context<Self>,
21557 ) -> bool {
21558 for (_, comments) in self.stored_review_comments.iter_mut() {
21559 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21560 comment.comment = new_comment;
21561 comment.is_editing = false;
21562 cx.emit(EditorEvent::ReviewCommentsChanged {
21563 total_count: self.total_review_comment_count(),
21564 });
21565 cx.notify();
21566 return true;
21567 }
21568 }
21569 false
21570 }
21571
21572 /// Sets a comment's editing state.
21573 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21574 for (_, comments) in self.stored_review_comments.iter_mut() {
21575 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21576 comment.is_editing = is_editing;
21577 cx.notify();
21578 return;
21579 }
21580 }
21581 }
21582
21583 /// Takes all stored comments from all hunks, clearing the storage.
21584 /// Returns a Vec of (hunk_key, comments) pairs.
21585 pub fn take_all_review_comments(
21586 &mut self,
21587 cx: &mut Context<Self>,
21588 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21589 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21590 self.dismiss_all_diff_review_overlays(cx);
21591 let comments = std::mem::take(&mut self.stored_review_comments);
21592 // Reset the ID counter since all comments have been taken
21593 self.next_review_comment_id = 0;
21594 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21595 cx.notify();
21596 comments
21597 }
21598
21599 /// Removes review comments whose anchors are no longer valid or whose
21600 /// associated diff hunks no longer exist.
21601 ///
21602 /// This should be called when the buffer changes to prevent orphaned comments
21603 /// from accumulating.
21604 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21605 let snapshot = self.buffer.read(cx).snapshot(cx);
21606 let original_count = self.total_review_comment_count();
21607
21608 // Remove comments with invalid hunk anchors
21609 self.stored_review_comments
21610 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21611
21612 // Also clean up individual comments with invalid anchor ranges
21613 for (_, comments) in &mut self.stored_review_comments {
21614 comments.retain(|comment| {
21615 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
21616 });
21617 }
21618
21619 // Remove empty hunk entries
21620 self.stored_review_comments
21621 .retain(|(_, comments)| !comments.is_empty());
21622
21623 let new_count = self.total_review_comment_count();
21624 if new_count != original_count {
21625 cx.emit(EditorEvent::ReviewCommentsChanged {
21626 total_count: new_count,
21627 });
21628 cx.notify();
21629 }
21630 }
21631
21632 /// Toggles the expanded state of the comments section in the overlay.
21633 pub fn toggle_review_comments_expanded(
21634 &mut self,
21635 _: &ToggleReviewCommentsExpanded,
21636 window: &mut Window,
21637 cx: &mut Context<Self>,
21638 ) {
21639 // Find the overlay that currently has focus, or use the first one
21640 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21641 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21642 overlay.comments_expanded = !overlay.comments_expanded;
21643 Some(overlay.hunk_key.clone())
21644 } else {
21645 None
21646 }
21647 });
21648
21649 // If no focused overlay found, toggle the first one
21650 let hunk_key = overlay_info.or_else(|| {
21651 self.diff_review_overlays.first_mut().map(|overlay| {
21652 overlay.comments_expanded = !overlay.comments_expanded;
21653 overlay.hunk_key.clone()
21654 })
21655 });
21656
21657 if let Some(hunk_key) = hunk_key {
21658 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21659 cx.notify();
21660 }
21661 }
21662
21663 /// Handles the EditReviewComment action - sets a comment into editing mode.
21664 pub fn edit_review_comment(
21665 &mut self,
21666 action: &EditReviewComment,
21667 window: &mut Window,
21668 cx: &mut Context<Self>,
21669 ) {
21670 let comment_id = action.id;
21671
21672 // Set the comment to editing mode
21673 self.set_comment_editing(comment_id, true, cx);
21674
21675 // Find the overlay that contains this comment and create an inline editor if needed
21676 // First, find which hunk this comment belongs to
21677 let hunk_key = self
21678 .stored_review_comments
21679 .iter()
21680 .find_map(|(key, comments)| {
21681 if comments.iter().any(|c| c.id == comment_id) {
21682 Some(key.clone())
21683 } else {
21684 None
21685 }
21686 });
21687
21688 let snapshot = self.buffer.read(cx).snapshot(cx);
21689 if let Some(hunk_key) = hunk_key {
21690 if let Some(overlay) = self
21691 .diff_review_overlays
21692 .iter_mut()
21693 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21694 {
21695 if let std::collections::hash_map::Entry::Vacant(entry) =
21696 overlay.inline_edit_editors.entry(comment_id)
21697 {
21698 // Find the comment text
21699 let comment_text = self
21700 .stored_review_comments
21701 .iter()
21702 .flat_map(|(_, comments)| comments)
21703 .find(|c| c.id == comment_id)
21704 .map(|c| c.comment.clone())
21705 .unwrap_or_default();
21706
21707 // Create inline editor
21708 let parent_editor = cx.entity().downgrade();
21709 let inline_editor = cx.new(|cx| {
21710 let mut editor = Editor::single_line(window, cx);
21711 editor.set_text(&*comment_text, window, cx);
21712 // Select all text for easy replacement
21713 editor.select_all(&crate::actions::SelectAll, window, cx);
21714 editor
21715 });
21716
21717 // Register the Newline action to confirm the edit
21718 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
21719 inline_editor.register_action({
21720 let parent_editor = parent_editor.clone();
21721 move |_: &crate::actions::Newline, window, cx| {
21722 if let Some(editor) = parent_editor.upgrade() {
21723 editor.update(cx, |editor, cx| {
21724 editor.confirm_edit_review_comment(comment_id, window, cx);
21725 });
21726 }
21727 }
21728 })
21729 });
21730
21731 // Store the subscription to keep the action handler alive
21732 overlay
21733 .inline_edit_subscriptions
21734 .insert(comment_id, subscription);
21735
21736 // Focus the inline editor
21737 let focus_handle = inline_editor.focus_handle(cx);
21738 window.focus(&focus_handle, cx);
21739
21740 entry.insert(inline_editor);
21741 }
21742 }
21743 }
21744
21745 cx.notify();
21746 }
21747
21748 /// Confirms an inline edit of a review comment.
21749 pub fn confirm_edit_review_comment(
21750 &mut self,
21751 comment_id: usize,
21752 _window: &mut Window,
21753 cx: &mut Context<Self>,
21754 ) {
21755 // Get the new text from the inline editor
21756 // Find the overlay containing this comment's inline editor
21757 let snapshot = self.buffer.read(cx).snapshot(cx);
21758 let hunk_key = self
21759 .stored_review_comments
21760 .iter()
21761 .find_map(|(key, comments)| {
21762 if comments.iter().any(|c| c.id == comment_id) {
21763 Some(key.clone())
21764 } else {
21765 None
21766 }
21767 });
21768
21769 let new_text = hunk_key
21770 .as_ref()
21771 .and_then(|hunk_key| {
21772 self.diff_review_overlays
21773 .iter()
21774 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21775 })
21776 .as_ref()
21777 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
21778 .map(|editor| editor.read(cx).text(cx).trim().to_string());
21779
21780 if let Some(new_text) = new_text {
21781 if !new_text.is_empty() {
21782 self.update_review_comment(comment_id, new_text, cx);
21783 }
21784 }
21785
21786 // Remove the inline editor and its subscription
21787 if let Some(hunk_key) = hunk_key {
21788 if let Some(overlay) = self
21789 .diff_review_overlays
21790 .iter_mut()
21791 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21792 {
21793 overlay.inline_edit_editors.remove(&comment_id);
21794 overlay.inline_edit_subscriptions.remove(&comment_id);
21795 }
21796 }
21797
21798 // Clear editing state
21799 self.set_comment_editing(comment_id, false, cx);
21800 }
21801
21802 /// Cancels an inline edit of a review comment.
21803 pub fn cancel_edit_review_comment(
21804 &mut self,
21805 comment_id: usize,
21806 _window: &mut Window,
21807 cx: &mut Context<Self>,
21808 ) {
21809 // Find which hunk this comment belongs to
21810 let hunk_key = self
21811 .stored_review_comments
21812 .iter()
21813 .find_map(|(key, comments)| {
21814 if comments.iter().any(|c| c.id == comment_id) {
21815 Some(key.clone())
21816 } else {
21817 None
21818 }
21819 });
21820
21821 // Remove the inline editor and its subscription
21822 if let Some(hunk_key) = hunk_key {
21823 let snapshot = self.buffer.read(cx).snapshot(cx);
21824 if let Some(overlay) = self
21825 .diff_review_overlays
21826 .iter_mut()
21827 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21828 {
21829 overlay.inline_edit_editors.remove(&comment_id);
21830 overlay.inline_edit_subscriptions.remove(&comment_id);
21831 }
21832 }
21833
21834 // Clear editing state
21835 self.set_comment_editing(comment_id, false, cx);
21836 }
21837
21838 /// Action handler for ConfirmEditReviewComment.
21839 pub fn confirm_edit_review_comment_action(
21840 &mut self,
21841 action: &ConfirmEditReviewComment,
21842 window: &mut Window,
21843 cx: &mut Context<Self>,
21844 ) {
21845 self.confirm_edit_review_comment(action.id, window, cx);
21846 }
21847
21848 /// Action handler for CancelEditReviewComment.
21849 pub fn cancel_edit_review_comment_action(
21850 &mut self,
21851 action: &CancelEditReviewComment,
21852 window: &mut Window,
21853 cx: &mut Context<Self>,
21854 ) {
21855 self.cancel_edit_review_comment(action.id, window, cx);
21856 }
21857
21858 /// Handles the DeleteReviewComment action - removes a comment.
21859 pub fn delete_review_comment(
21860 &mut self,
21861 action: &DeleteReviewComment,
21862 window: &mut Window,
21863 cx: &mut Context<Self>,
21864 ) {
21865 // Get the hunk key before removing the comment
21866 // Find the hunk key from the comment itself
21867 let comment_id = action.id;
21868 let hunk_key = self
21869 .stored_review_comments
21870 .iter()
21871 .find_map(|(key, comments)| {
21872 if comments.iter().any(|c| c.id == comment_id) {
21873 Some(key.clone())
21874 } else {
21875 None
21876 }
21877 });
21878
21879 // Also get it from the overlay for refresh purposes
21880 let overlay_hunk_key = self
21881 .diff_review_overlays
21882 .first()
21883 .map(|o| o.hunk_key.clone());
21884
21885 self.remove_review_comment(action.id, cx);
21886
21887 // Refresh the overlay height after removing a comment
21888 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
21889 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21890 }
21891 }
21892
21893 fn render_diff_review_overlay(
21894 prompt_editor: &Entity<Editor>,
21895 hunk_key: &DiffHunkKey,
21896 editor_handle: &WeakEntity<Editor>,
21897 cx: &mut BlockContext,
21898 ) -> AnyElement {
21899 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
21900 if ranges.is_empty() {
21901 return None;
21902 }
21903 let formatted: Vec<String> = ranges
21904 .iter()
21905 .map(|(start, end)| {
21906 let start_line = start + 1;
21907 let end_line = end + 1;
21908 if start_line == end_line {
21909 format!("Line {start_line}")
21910 } else {
21911 format!("Lines {start_line}-{end_line}")
21912 }
21913 })
21914 .collect();
21915 // Don't show label for single line in single excerpt
21916 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
21917 return None;
21918 }
21919 Some(formatted.join(" ⋯ "))
21920 }
21921
21922 let theme = cx.theme();
21923 let colors = theme.colors();
21924
21925 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
21926 editor_handle
21927 .upgrade()
21928 .map(|editor| {
21929 let editor = editor.read(cx);
21930 let snapshot = editor.buffer().read(cx).snapshot(cx);
21931 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
21932 let (expanded, editors, avatar_uri, line_ranges) = editor
21933 .diff_review_overlays
21934 .iter()
21935 .find(|overlay| {
21936 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
21937 })
21938 .map(|o| {
21939 let start_point = o.anchor_range.start.to_point(&snapshot);
21940 let end_point = o.anchor_range.end.to_point(&snapshot);
21941 // Get line ranges per excerpt to detect discontinuities
21942 let buffer_ranges =
21943 snapshot.range_to_buffer_ranges(start_point..end_point);
21944 let ranges: Vec<(u32, u32)> = buffer_ranges
21945 .iter()
21946 .map(|(buffer, range, _)| {
21947 let start = buffer.offset_to_point(range.start.0).row;
21948 let end = buffer.offset_to_point(range.end.0).row;
21949 (start, end)
21950 })
21951 .collect();
21952 (
21953 o.comments_expanded,
21954 o.inline_edit_editors.clone(),
21955 o.user_avatar_uri.clone(),
21956 if ranges.is_empty() {
21957 None
21958 } else {
21959 Some(ranges)
21960 },
21961 )
21962 })
21963 .unwrap_or((true, HashMap::default(), None, None));
21964 (comments, expanded, editors, avatar_uri, line_ranges)
21965 })
21966 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
21967
21968 let comment_count = comments.len();
21969 let avatar_size = px(20.);
21970 let action_icon_size = IconSize::XSmall;
21971
21972 v_flex()
21973 .w_full()
21974 .bg(colors.editor_background)
21975 .border_b_1()
21976 .border_color(colors.border)
21977 .px_2()
21978 .pb_2()
21979 .gap_2()
21980 // Line range indicator (only shown for multi-line selections or multiple excerpts)
21981 .when_some(line_ranges, |el, ranges| {
21982 let label = format_line_ranges(&ranges);
21983 if let Some(label) = label {
21984 el.child(
21985 h_flex()
21986 .w_full()
21987 .px_2()
21988 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
21989 )
21990 } else {
21991 el
21992 }
21993 })
21994 // Top row: editable input with user's avatar
21995 .child(
21996 h_flex()
21997 .w_full()
21998 .items_center()
21999 .gap_2()
22000 .px_2()
22001 .py_1p5()
22002 .rounded_md()
22003 .bg(colors.surface_background)
22004 .child(
22005 div()
22006 .size(avatar_size)
22007 .flex_shrink_0()
22008 .rounded_full()
22009 .overflow_hidden()
22010 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22011 Avatar::new(avatar_uri.clone())
22012 .size(avatar_size)
22013 .into_any_element()
22014 } else {
22015 Icon::new(IconName::Person)
22016 .size(IconSize::Small)
22017 .color(ui::Color::Muted)
22018 .into_any_element()
22019 }),
22020 )
22021 .child(
22022 div()
22023 .flex_1()
22024 .border_1()
22025 .border_color(colors.border)
22026 .rounded_md()
22027 .bg(colors.editor_background)
22028 .px_2()
22029 .py_1()
22030 .child(prompt_editor.clone()),
22031 )
22032 .child(
22033 h_flex()
22034 .flex_shrink_0()
22035 .gap_1()
22036 .child(
22037 IconButton::new("diff-review-close", IconName::Close)
22038 .icon_color(ui::Color::Muted)
22039 .icon_size(action_icon_size)
22040 .tooltip(Tooltip::text("Close"))
22041 .on_click(|_, window, cx| {
22042 window
22043 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22044 }),
22045 )
22046 .child(
22047 IconButton::new("diff-review-add", IconName::Return)
22048 .icon_color(ui::Color::Muted)
22049 .icon_size(action_icon_size)
22050 .tooltip(Tooltip::text("Add comment"))
22051 .on_click(|_, window, cx| {
22052 window.dispatch_action(
22053 Box::new(crate::actions::SubmitDiffReviewComment),
22054 cx,
22055 );
22056 }),
22057 ),
22058 ),
22059 )
22060 // Expandable comments section (only shown when there are comments)
22061 .when(comment_count > 0, |el| {
22062 el.child(Self::render_comments_section(
22063 comments,
22064 comments_expanded,
22065 inline_editors,
22066 user_avatar_uri,
22067 avatar_size,
22068 action_icon_size,
22069 colors,
22070 ))
22071 })
22072 .into_any_element()
22073 }
22074
22075 fn render_comments_section(
22076 comments: Vec<StoredReviewComment>,
22077 expanded: bool,
22078 inline_editors: HashMap<usize, Entity<Editor>>,
22079 user_avatar_uri: Option<SharedUri>,
22080 avatar_size: Pixels,
22081 action_icon_size: IconSize,
22082 colors: &theme::ThemeColors,
22083 ) -> impl IntoElement {
22084 let comment_count = comments.len();
22085
22086 v_flex()
22087 .w_full()
22088 .gap_1()
22089 // Header with expand/collapse toggle
22090 .child(
22091 h_flex()
22092 .id("review-comments-header")
22093 .w_full()
22094 .items_center()
22095 .gap_1()
22096 .px_2()
22097 .py_1()
22098 .cursor_pointer()
22099 .rounded_md()
22100 .hover(|style| style.bg(colors.ghost_element_hover))
22101 .on_click(|_, window: &mut Window, cx| {
22102 window.dispatch_action(
22103 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22104 cx,
22105 );
22106 })
22107 .child(
22108 Icon::new(if expanded {
22109 IconName::ChevronDown
22110 } else {
22111 IconName::ChevronRight
22112 })
22113 .size(IconSize::Small)
22114 .color(ui::Color::Muted),
22115 )
22116 .child(
22117 Label::new(format!(
22118 "{} Comment{}",
22119 comment_count,
22120 if comment_count == 1 { "" } else { "s" }
22121 ))
22122 .size(LabelSize::Small)
22123 .color(Color::Muted),
22124 ),
22125 )
22126 // Comments list (when expanded)
22127 .when(expanded, |el| {
22128 el.children(comments.into_iter().map(|comment| {
22129 let inline_editor = inline_editors.get(&comment.id).cloned();
22130 Self::render_comment_row(
22131 comment,
22132 inline_editor,
22133 user_avatar_uri.clone(),
22134 avatar_size,
22135 action_icon_size,
22136 colors,
22137 )
22138 }))
22139 })
22140 }
22141
22142 fn render_comment_row(
22143 comment: StoredReviewComment,
22144 inline_editor: Option<Entity<Editor>>,
22145 user_avatar_uri: Option<SharedUri>,
22146 avatar_size: Pixels,
22147 action_icon_size: IconSize,
22148 colors: &theme::ThemeColors,
22149 ) -> impl IntoElement {
22150 let comment_id = comment.id;
22151 let is_editing = inline_editor.is_some();
22152
22153 h_flex()
22154 .w_full()
22155 .items_center()
22156 .gap_2()
22157 .px_2()
22158 .py_1p5()
22159 .rounded_md()
22160 .bg(colors.surface_background)
22161 .child(
22162 div()
22163 .size(avatar_size)
22164 .flex_shrink_0()
22165 .rounded_full()
22166 .overflow_hidden()
22167 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22168 Avatar::new(avatar_uri.clone())
22169 .size(avatar_size)
22170 .into_any_element()
22171 } else {
22172 Icon::new(IconName::Person)
22173 .size(IconSize::Small)
22174 .color(ui::Color::Muted)
22175 .into_any_element()
22176 }),
22177 )
22178 .child(if let Some(editor) = inline_editor {
22179 // Inline edit mode: show an editable text field
22180 div()
22181 .flex_1()
22182 .border_1()
22183 .border_color(colors.border)
22184 .rounded_md()
22185 .bg(colors.editor_background)
22186 .px_2()
22187 .py_1()
22188 .child(editor)
22189 .into_any_element()
22190 } else {
22191 // Display mode: show the comment text
22192 div()
22193 .flex_1()
22194 .text_sm()
22195 .text_color(colors.text)
22196 .child(comment.comment)
22197 .into_any_element()
22198 })
22199 .child(if is_editing {
22200 // Editing mode: show close and confirm buttons
22201 h_flex()
22202 .gap_1()
22203 .child(
22204 IconButton::new(
22205 format!("diff-review-cancel-edit-{comment_id}"),
22206 IconName::Close,
22207 )
22208 .icon_color(ui::Color::Muted)
22209 .icon_size(action_icon_size)
22210 .tooltip(Tooltip::text("Cancel"))
22211 .on_click(move |_, window, cx| {
22212 window.dispatch_action(
22213 Box::new(crate::actions::CancelEditReviewComment {
22214 id: comment_id,
22215 }),
22216 cx,
22217 );
22218 }),
22219 )
22220 .child(
22221 IconButton::new(
22222 format!("diff-review-confirm-edit-{comment_id}"),
22223 IconName::Return,
22224 )
22225 .icon_color(ui::Color::Muted)
22226 .icon_size(action_icon_size)
22227 .tooltip(Tooltip::text("Confirm"))
22228 .on_click(move |_, window, cx| {
22229 window.dispatch_action(
22230 Box::new(crate::actions::ConfirmEditReviewComment {
22231 id: comment_id,
22232 }),
22233 cx,
22234 );
22235 }),
22236 )
22237 .into_any_element()
22238 } else {
22239 // Display mode: no action buttons for now (edit/delete not yet implemented)
22240 gpui::Empty.into_any_element()
22241 })
22242 }
22243
22244 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22245 if self.display_map.read(cx).masked != masked {
22246 self.display_map.update(cx, |map, _| map.masked = masked);
22247 }
22248 cx.notify()
22249 }
22250
22251 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22252 self.show_wrap_guides = Some(show_wrap_guides);
22253 cx.notify();
22254 }
22255
22256 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22257 self.show_indent_guides = Some(show_indent_guides);
22258 cx.notify();
22259 }
22260
22261 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22262 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22263 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22264 && let Some(dir) = file.abs_path(cx).parent()
22265 {
22266 return Some(dir.to_owned());
22267 }
22268 }
22269
22270 None
22271 }
22272
22273 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22274 self.active_excerpt(cx)?
22275 .1
22276 .read(cx)
22277 .file()
22278 .and_then(|f| f.as_local())
22279 }
22280
22281 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22282 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22283 let buffer = buffer.read(cx);
22284 if let Some(project_path) = buffer.project_path(cx) {
22285 let project = self.project()?.read(cx);
22286 project.absolute_path(&project_path, cx)
22287 } else {
22288 buffer
22289 .file()
22290 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22291 }
22292 })
22293 }
22294
22295 pub fn reveal_in_finder(
22296 &mut self,
22297 _: &RevealInFileManager,
22298 _window: &mut Window,
22299 cx: &mut Context<Self>,
22300 ) {
22301 if let Some(path) = self.target_file_abs_path(cx) {
22302 if let Some(project) = self.project() {
22303 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22304 } else {
22305 cx.reveal_path(&path);
22306 }
22307 }
22308 }
22309
22310 pub fn copy_path(
22311 &mut self,
22312 _: &zed_actions::workspace::CopyPath,
22313 _window: &mut Window,
22314 cx: &mut Context<Self>,
22315 ) {
22316 if let Some(path) = self.target_file_abs_path(cx)
22317 && let Some(path) = path.to_str()
22318 {
22319 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22320 } else {
22321 cx.propagate();
22322 }
22323 }
22324
22325 pub fn copy_relative_path(
22326 &mut self,
22327 _: &zed_actions::workspace::CopyRelativePath,
22328 _window: &mut Window,
22329 cx: &mut Context<Self>,
22330 ) {
22331 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22332 let project = self.project()?.read(cx);
22333 let path = buffer.read(cx).file()?.path();
22334 let path = path.display(project.path_style(cx));
22335 Some(path)
22336 }) {
22337 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22338 } else {
22339 cx.propagate();
22340 }
22341 }
22342
22343 /// Returns the project path for the editor's buffer, if any buffer is
22344 /// opened in the editor.
22345 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22346 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22347 buffer.read(cx).project_path(cx)
22348 } else {
22349 None
22350 }
22351 }
22352
22353 // Returns true if the editor handled a go-to-line request
22354 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22355 maybe!({
22356 let breakpoint_store = self.breakpoint_store.as_ref()?;
22357
22358 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22359 else {
22360 self.clear_row_highlights::<ActiveDebugLine>();
22361 return None;
22362 };
22363
22364 let position = active_stack_frame.position;
22365 let buffer_id = position.buffer_id?;
22366 let snapshot = self
22367 .project
22368 .as_ref()?
22369 .read(cx)
22370 .buffer_for_id(buffer_id, cx)?
22371 .read(cx)
22372 .snapshot();
22373
22374 let mut handled = false;
22375 for (id, ExcerptRange { context, .. }) in
22376 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22377 {
22378 if context.start.cmp(&position, &snapshot).is_ge()
22379 || context.end.cmp(&position, &snapshot).is_lt()
22380 {
22381 continue;
22382 }
22383 let snapshot = self.buffer.read(cx).snapshot(cx);
22384 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22385
22386 handled = true;
22387 self.clear_row_highlights::<ActiveDebugLine>();
22388
22389 self.go_to_line::<ActiveDebugLine>(
22390 multibuffer_anchor,
22391 Some(cx.theme().colors().editor_debugger_active_line_background),
22392 window,
22393 cx,
22394 );
22395
22396 cx.notify();
22397 }
22398
22399 handled.then_some(())
22400 })
22401 .is_some()
22402 }
22403
22404 pub fn copy_file_name_without_extension(
22405 &mut self,
22406 _: &CopyFileNameWithoutExtension,
22407 _: &mut Window,
22408 cx: &mut Context<Self>,
22409 ) {
22410 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22411 let file = buffer.read(cx).file()?;
22412 file.path().file_stem()
22413 }) {
22414 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22415 }
22416 }
22417
22418 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22419 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22420 let file = buffer.read(cx).file()?;
22421 Some(file.file_name(cx))
22422 }) {
22423 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22424 }
22425 }
22426
22427 pub fn toggle_git_blame(
22428 &mut self,
22429 _: &::git::Blame,
22430 window: &mut Window,
22431 cx: &mut Context<Self>,
22432 ) {
22433 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22434
22435 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22436 self.start_git_blame(true, window, cx);
22437 }
22438
22439 cx.notify();
22440 }
22441
22442 pub fn toggle_git_blame_inline(
22443 &mut self,
22444 _: &ToggleGitBlameInline,
22445 window: &mut Window,
22446 cx: &mut Context<Self>,
22447 ) {
22448 self.toggle_git_blame_inline_internal(true, window, cx);
22449 cx.notify();
22450 }
22451
22452 pub fn open_git_blame_commit(
22453 &mut self,
22454 _: &OpenGitBlameCommit,
22455 window: &mut Window,
22456 cx: &mut Context<Self>,
22457 ) {
22458 self.open_git_blame_commit_internal(window, cx);
22459 }
22460
22461 fn open_git_blame_commit_internal(
22462 &mut self,
22463 window: &mut Window,
22464 cx: &mut Context<Self>,
22465 ) -> Option<()> {
22466 let blame = self.blame.as_ref()?;
22467 let snapshot = self.snapshot(window, cx);
22468 let cursor = self
22469 .selections
22470 .newest::<Point>(&snapshot.display_snapshot)
22471 .head();
22472 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22473 let (_, blame_entry) = blame
22474 .update(cx, |blame, cx| {
22475 blame
22476 .blame_for_rows(
22477 &[RowInfo {
22478 buffer_id: Some(buffer.remote_id()),
22479 buffer_row: Some(point.row),
22480 ..Default::default()
22481 }],
22482 cx,
22483 )
22484 .next()
22485 })
22486 .flatten()?;
22487 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22488 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22489 let workspace = self.workspace()?.downgrade();
22490 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22491 None
22492 }
22493
22494 pub fn git_blame_inline_enabled(&self) -> bool {
22495 self.git_blame_inline_enabled
22496 }
22497
22498 pub fn toggle_selection_menu(
22499 &mut self,
22500 _: &ToggleSelectionMenu,
22501 _: &mut Window,
22502 cx: &mut Context<Self>,
22503 ) {
22504 self.show_selection_menu = self
22505 .show_selection_menu
22506 .map(|show_selections_menu| !show_selections_menu)
22507 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22508
22509 cx.notify();
22510 }
22511
22512 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22513 self.show_selection_menu
22514 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22515 }
22516
22517 fn start_git_blame(
22518 &mut self,
22519 user_triggered: bool,
22520 window: &mut Window,
22521 cx: &mut Context<Self>,
22522 ) {
22523 if let Some(project) = self.project() {
22524 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22525 && buffer.read(cx).file().is_none()
22526 {
22527 return;
22528 }
22529
22530 let focused = self.focus_handle(cx).contains_focused(window, cx);
22531
22532 let project = project.clone();
22533 let blame = cx
22534 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22535 self.blame_subscription =
22536 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22537 self.blame = Some(blame);
22538 }
22539 }
22540
22541 fn toggle_git_blame_inline_internal(
22542 &mut self,
22543 user_triggered: bool,
22544 window: &mut Window,
22545 cx: &mut Context<Self>,
22546 ) {
22547 if self.git_blame_inline_enabled {
22548 self.git_blame_inline_enabled = false;
22549 self.show_git_blame_inline = false;
22550 self.show_git_blame_inline_delay_task.take();
22551 } else {
22552 self.git_blame_inline_enabled = true;
22553 self.start_git_blame_inline(user_triggered, window, cx);
22554 }
22555
22556 cx.notify();
22557 }
22558
22559 fn start_git_blame_inline(
22560 &mut self,
22561 user_triggered: bool,
22562 window: &mut Window,
22563 cx: &mut Context<Self>,
22564 ) {
22565 self.start_git_blame(user_triggered, window, cx);
22566
22567 if ProjectSettings::get_global(cx)
22568 .git
22569 .inline_blame_delay()
22570 .is_some()
22571 {
22572 self.start_inline_blame_timer(window, cx);
22573 } else {
22574 self.show_git_blame_inline = true
22575 }
22576 }
22577
22578 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22579 self.blame.as_ref()
22580 }
22581
22582 pub fn show_git_blame_gutter(&self) -> bool {
22583 self.show_git_blame_gutter
22584 }
22585
22586 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22587 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22588 }
22589
22590 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22591 self.show_git_blame_inline
22592 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22593 && !self.newest_selection_head_on_empty_line(cx)
22594 && self.has_blame_entries(cx)
22595 }
22596
22597 fn has_blame_entries(&self, cx: &App) -> bool {
22598 self.blame()
22599 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22600 }
22601
22602 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22603 let cursor_anchor = self.selections.newest_anchor().head();
22604
22605 let snapshot = self.buffer.read(cx).snapshot(cx);
22606 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22607
22608 snapshot.line_len(buffer_row) == 0
22609 }
22610
22611 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22612 let buffer_and_selection = maybe!({
22613 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22614 let selection_range = selection.range();
22615
22616 let multi_buffer = self.buffer().read(cx);
22617 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22618 let buffer_ranges = multi_buffer_snapshot
22619 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
22620
22621 let (buffer, range, _) = if selection.reversed {
22622 buffer_ranges.first()
22623 } else {
22624 buffer_ranges.last()
22625 }?;
22626
22627 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
22628 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
22629
22630 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22631 let selection = start_row_in_buffer..end_row_in_buffer;
22632
22633 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
22634 };
22635
22636 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22637 let (mut translated, _, _) = buffer_diff_snapshot.points_to_base_text_points(
22638 [
22639 Point::new(start_row_in_buffer, 0),
22640 Point::new(end_row_in_buffer, 0),
22641 ],
22642 buffer,
22643 );
22644 let start_row = translated.next().unwrap().start.row;
22645 let end_row = translated.next().unwrap().end.row;
22646
22647 Some((
22648 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22649 start_row..end_row,
22650 ))
22651 });
22652
22653 let Some((buffer, selection)) = buffer_and_selection else {
22654 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22655 };
22656
22657 let Some(project) = self.project() else {
22658 return Task::ready(Err(anyhow!("editor does not have project")));
22659 };
22660
22661 project.update(cx, |project, cx| {
22662 project.get_permalink_to_line(&buffer, selection, cx)
22663 })
22664 }
22665
22666 pub fn copy_permalink_to_line(
22667 &mut self,
22668 _: &CopyPermalinkToLine,
22669 window: &mut Window,
22670 cx: &mut Context<Self>,
22671 ) {
22672 let permalink_task = self.get_permalink_to_line(cx);
22673 let workspace = self.workspace();
22674
22675 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22676 Ok(permalink) => {
22677 cx.update(|_, cx| {
22678 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22679 })
22680 .ok();
22681 }
22682 Err(err) => {
22683 let message = format!("Failed to copy permalink: {err}");
22684
22685 anyhow::Result::<()>::Err(err).log_err();
22686
22687 if let Some(workspace) = workspace {
22688 workspace
22689 .update_in(cx, |workspace, _, cx| {
22690 struct CopyPermalinkToLine;
22691
22692 workspace.show_toast(
22693 Toast::new(
22694 NotificationId::unique::<CopyPermalinkToLine>(),
22695 message,
22696 ),
22697 cx,
22698 )
22699 })
22700 .ok();
22701 }
22702 }
22703 })
22704 .detach();
22705 }
22706
22707 pub fn copy_file_location(
22708 &mut self,
22709 _: &CopyFileLocation,
22710 _: &mut Window,
22711 cx: &mut Context<Self>,
22712 ) {
22713 let selection = self
22714 .selections
22715 .newest::<Point>(&self.display_snapshot(cx))
22716 .start
22717 .row
22718 + 1;
22719 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22720 let project = self.project()?.read(cx);
22721 let file = buffer.read(cx).file()?;
22722 let path = file.path().display(project.path_style(cx));
22723
22724 Some(format!("{path}:{selection}"))
22725 }) {
22726 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
22727 }
22728 }
22729
22730 pub fn open_permalink_to_line(
22731 &mut self,
22732 _: &OpenPermalinkToLine,
22733 window: &mut Window,
22734 cx: &mut Context<Self>,
22735 ) {
22736 let permalink_task = self.get_permalink_to_line(cx);
22737 let workspace = self.workspace();
22738
22739 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22740 Ok(permalink) => {
22741 cx.update(|_, cx| {
22742 cx.open_url(permalink.as_ref());
22743 })
22744 .ok();
22745 }
22746 Err(err) => {
22747 let message = format!("Failed to open permalink: {err}");
22748
22749 anyhow::Result::<()>::Err(err).log_err();
22750
22751 if let Some(workspace) = workspace {
22752 workspace.update(cx, |workspace, cx| {
22753 struct OpenPermalinkToLine;
22754
22755 workspace.show_toast(
22756 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
22757 cx,
22758 )
22759 });
22760 }
22761 }
22762 })
22763 .detach();
22764 }
22765
22766 pub fn insert_uuid_v4(
22767 &mut self,
22768 _: &InsertUuidV4,
22769 window: &mut Window,
22770 cx: &mut Context<Self>,
22771 ) {
22772 self.insert_uuid(UuidVersion::V4, window, cx);
22773 }
22774
22775 pub fn insert_uuid_v7(
22776 &mut self,
22777 _: &InsertUuidV7,
22778 window: &mut Window,
22779 cx: &mut Context<Self>,
22780 ) {
22781 self.insert_uuid(UuidVersion::V7, window, cx);
22782 }
22783
22784 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
22785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
22786 self.transact(window, cx, |this, window, cx| {
22787 let edits = this
22788 .selections
22789 .all::<Point>(&this.display_snapshot(cx))
22790 .into_iter()
22791 .map(|selection| {
22792 let uuid = match version {
22793 UuidVersion::V4 => uuid::Uuid::new_v4(),
22794 UuidVersion::V7 => uuid::Uuid::now_v7(),
22795 };
22796
22797 (selection.range(), uuid.to_string())
22798 });
22799 this.edit(edits, cx);
22800 this.refresh_edit_prediction(true, false, window, cx);
22801 });
22802 }
22803
22804 pub fn open_selections_in_multibuffer(
22805 &mut self,
22806 _: &OpenSelectionsInMultibuffer,
22807 window: &mut Window,
22808 cx: &mut Context<Self>,
22809 ) {
22810 let multibuffer = self.buffer.read(cx);
22811
22812 let Some(buffer) = multibuffer.as_singleton() else {
22813 return;
22814 };
22815
22816 let Some(workspace) = self.workspace() else {
22817 return;
22818 };
22819
22820 let title = multibuffer.title(cx).to_string();
22821
22822 let locations = self
22823 .selections
22824 .all_anchors(&self.display_snapshot(cx))
22825 .iter()
22826 .map(|selection| {
22827 (
22828 buffer.clone(),
22829 (selection.start.text_anchor..selection.end.text_anchor)
22830 .to_point(buffer.read(cx)),
22831 )
22832 })
22833 .into_group_map();
22834
22835 cx.spawn_in(window, async move |_, cx| {
22836 workspace.update_in(cx, |workspace, window, cx| {
22837 Self::open_locations_in_multibuffer(
22838 workspace,
22839 locations,
22840 format!("Selections for '{title}'"),
22841 false,
22842 false,
22843 MultibufferSelectionMode::All,
22844 window,
22845 cx,
22846 );
22847 })
22848 })
22849 .detach();
22850 }
22851
22852 /// Adds a row highlight for the given range. If a row has multiple highlights, the
22853 /// last highlight added will be used.
22854 ///
22855 /// If the range ends at the beginning of a line, then that line will not be highlighted.
22856 pub fn highlight_rows<T: 'static>(
22857 &mut self,
22858 range: Range<Anchor>,
22859 color: Hsla,
22860 options: RowHighlightOptions,
22861 cx: &mut Context<Self>,
22862 ) {
22863 let snapshot = self.buffer().read(cx).snapshot(cx);
22864 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
22865 let ix = row_highlights.binary_search_by(|highlight| {
22866 Ordering::Equal
22867 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
22868 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
22869 });
22870
22871 if let Err(mut ix) = ix {
22872 let index = post_inc(&mut self.highlight_order);
22873
22874 // If this range intersects with the preceding highlight, then merge it with
22875 // the preceding highlight. Otherwise insert a new highlight.
22876 let mut merged = false;
22877 if ix > 0 {
22878 let prev_highlight = &mut row_highlights[ix - 1];
22879 if prev_highlight
22880 .range
22881 .end
22882 .cmp(&range.start, &snapshot)
22883 .is_ge()
22884 {
22885 ix -= 1;
22886 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
22887 prev_highlight.range.end = range.end;
22888 }
22889 merged = true;
22890 prev_highlight.index = index;
22891 prev_highlight.color = color;
22892 prev_highlight.options = options;
22893 }
22894 }
22895
22896 if !merged {
22897 row_highlights.insert(
22898 ix,
22899 RowHighlight {
22900 range,
22901 index,
22902 color,
22903 options,
22904 type_id: TypeId::of::<T>(),
22905 },
22906 );
22907 }
22908
22909 // If any of the following highlights intersect with this one, merge them.
22910 while let Some(next_highlight) = row_highlights.get(ix + 1) {
22911 let highlight = &row_highlights[ix];
22912 if next_highlight
22913 .range
22914 .start
22915 .cmp(&highlight.range.end, &snapshot)
22916 .is_le()
22917 {
22918 if next_highlight
22919 .range
22920 .end
22921 .cmp(&highlight.range.end, &snapshot)
22922 .is_gt()
22923 {
22924 row_highlights[ix].range.end = next_highlight.range.end;
22925 }
22926 row_highlights.remove(ix + 1);
22927 } else {
22928 break;
22929 }
22930 }
22931 }
22932 }
22933
22934 /// Remove any highlighted row ranges of the given type that intersect the
22935 /// given ranges.
22936 pub fn remove_highlighted_rows<T: 'static>(
22937 &mut self,
22938 ranges_to_remove: Vec<Range<Anchor>>,
22939 cx: &mut Context<Self>,
22940 ) {
22941 let snapshot = self.buffer().read(cx).snapshot(cx);
22942 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
22943 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
22944 row_highlights.retain(|highlight| {
22945 while let Some(range_to_remove) = ranges_to_remove.peek() {
22946 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
22947 Ordering::Less | Ordering::Equal => {
22948 ranges_to_remove.next();
22949 }
22950 Ordering::Greater => {
22951 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
22952 Ordering::Less | Ordering::Equal => {
22953 return false;
22954 }
22955 Ordering::Greater => break,
22956 }
22957 }
22958 }
22959 }
22960
22961 true
22962 })
22963 }
22964
22965 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
22966 pub fn clear_row_highlights<T: 'static>(&mut self) {
22967 self.highlighted_rows.remove(&TypeId::of::<T>());
22968 }
22969
22970 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
22971 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
22972 self.highlighted_rows
22973 .get(&TypeId::of::<T>())
22974 .map_or(&[] as &[_], |vec| vec.as_slice())
22975 .iter()
22976 .map(|highlight| (highlight.range.clone(), highlight.color))
22977 }
22978
22979 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
22980 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
22981 /// Allows to ignore certain kinds of highlights.
22982 pub fn highlighted_display_rows(
22983 &self,
22984 window: &mut Window,
22985 cx: &mut App,
22986 ) -> BTreeMap<DisplayRow, LineHighlight> {
22987 let snapshot = self.snapshot(window, cx);
22988 let mut used_highlight_orders = HashMap::default();
22989 self.highlighted_rows
22990 .iter()
22991 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
22992 .fold(
22993 BTreeMap::<DisplayRow, LineHighlight>::new(),
22994 |mut unique_rows, highlight| {
22995 let start = highlight.range.start.to_display_point(&snapshot);
22996 let end = highlight.range.end.to_display_point(&snapshot);
22997 let start_row = start.row().0;
22998 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
22999 {
23000 end.row().0.saturating_sub(1)
23001 } else {
23002 end.row().0
23003 };
23004 for row in start_row..=end_row {
23005 let used_index =
23006 used_highlight_orders.entry(row).or_insert(highlight.index);
23007 if highlight.index >= *used_index {
23008 *used_index = highlight.index;
23009 unique_rows.insert(
23010 DisplayRow(row),
23011 LineHighlight {
23012 include_gutter: highlight.options.include_gutter,
23013 border: None,
23014 background: highlight.color.into(),
23015 type_id: Some(highlight.type_id),
23016 },
23017 );
23018 }
23019 }
23020 unique_rows
23021 },
23022 )
23023 }
23024
23025 pub fn highlighted_display_row_for_autoscroll(
23026 &self,
23027 snapshot: &DisplaySnapshot,
23028 ) -> Option<DisplayRow> {
23029 self.highlighted_rows
23030 .values()
23031 .flat_map(|highlighted_rows| highlighted_rows.iter())
23032 .filter_map(|highlight| {
23033 if highlight.options.autoscroll {
23034 Some(highlight.range.start.to_display_point(snapshot).row())
23035 } else {
23036 None
23037 }
23038 })
23039 .min()
23040 }
23041
23042 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23043 self.highlight_background::<SearchWithinRange>(
23044 ranges,
23045 |_, colors| colors.colors().editor_document_highlight_read_background,
23046 cx,
23047 )
23048 }
23049
23050 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23051 self.breadcrumb_header = Some(new_header);
23052 }
23053
23054 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23055 self.clear_background_highlights::<SearchWithinRange>(cx);
23056 }
23057
23058 pub fn highlight_background<T: 'static>(
23059 &mut self,
23060 ranges: &[Range<Anchor>],
23061 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23062 cx: &mut Context<Self>,
23063 ) {
23064 self.background_highlights.insert(
23065 HighlightKey::Type(TypeId::of::<T>()),
23066 (Arc::new(color_fetcher), Arc::from(ranges)),
23067 );
23068 self.scrollbar_marker_state.dirty = true;
23069 cx.notify();
23070 }
23071
23072 pub fn highlight_background_key<T: 'static>(
23073 &mut self,
23074 key: usize,
23075 ranges: &[Range<Anchor>],
23076 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23077 cx: &mut Context<Self>,
23078 ) {
23079 self.background_highlights.insert(
23080 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23081 (Arc::new(color_fetcher), Arc::from(ranges)),
23082 );
23083 self.scrollbar_marker_state.dirty = true;
23084 cx.notify();
23085 }
23086
23087 pub fn clear_background_highlights<T: 'static>(
23088 &mut self,
23089 cx: &mut Context<Self>,
23090 ) -> Option<BackgroundHighlight> {
23091 let text_highlights = self
23092 .background_highlights
23093 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
23094 if !text_highlights.1.is_empty() {
23095 self.scrollbar_marker_state.dirty = true;
23096 cx.notify();
23097 }
23098 Some(text_highlights)
23099 }
23100
23101 pub fn clear_background_highlights_key<T: 'static>(
23102 &mut self,
23103 key: usize,
23104 cx: &mut Context<Self>,
23105 ) -> Option<BackgroundHighlight> {
23106 let text_highlights = self
23107 .background_highlights
23108 .remove(&HighlightKey::TypePlus(TypeId::of::<T>(), key))?;
23109 if !text_highlights.1.is_empty() {
23110 self.scrollbar_marker_state.dirty = true;
23111 cx.notify();
23112 }
23113 Some(text_highlights)
23114 }
23115
23116 pub fn highlight_gutter<T: 'static>(
23117 &mut self,
23118 ranges: impl Into<Vec<Range<Anchor>>>,
23119 color_fetcher: fn(&App) -> Hsla,
23120 cx: &mut Context<Self>,
23121 ) {
23122 self.gutter_highlights
23123 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23124 cx.notify();
23125 }
23126
23127 pub fn clear_gutter_highlights<T: 'static>(
23128 &mut self,
23129 cx: &mut Context<Self>,
23130 ) -> Option<GutterHighlight> {
23131 cx.notify();
23132 self.gutter_highlights.remove(&TypeId::of::<T>())
23133 }
23134
23135 pub fn insert_gutter_highlight<T: 'static>(
23136 &mut self,
23137 range: Range<Anchor>,
23138 color_fetcher: fn(&App) -> Hsla,
23139 cx: &mut Context<Self>,
23140 ) {
23141 let snapshot = self.buffer().read(cx).snapshot(cx);
23142 let mut highlights = self
23143 .gutter_highlights
23144 .remove(&TypeId::of::<T>())
23145 .map(|(_, highlights)| highlights)
23146 .unwrap_or_default();
23147 let ix = highlights.binary_search_by(|highlight| {
23148 Ordering::Equal
23149 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23150 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23151 });
23152 if let Err(ix) = ix {
23153 highlights.insert(ix, range);
23154 }
23155 self.gutter_highlights
23156 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23157 }
23158
23159 pub fn remove_gutter_highlights<T: 'static>(
23160 &mut self,
23161 ranges_to_remove: Vec<Range<Anchor>>,
23162 cx: &mut Context<Self>,
23163 ) {
23164 let snapshot = self.buffer().read(cx).snapshot(cx);
23165 let Some((color_fetcher, mut gutter_highlights)) =
23166 self.gutter_highlights.remove(&TypeId::of::<T>())
23167 else {
23168 return;
23169 };
23170 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23171 gutter_highlights.retain(|highlight| {
23172 while let Some(range_to_remove) = ranges_to_remove.peek() {
23173 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23174 Ordering::Less | Ordering::Equal => {
23175 ranges_to_remove.next();
23176 }
23177 Ordering::Greater => {
23178 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23179 Ordering::Less | Ordering::Equal => {
23180 return false;
23181 }
23182 Ordering::Greater => break,
23183 }
23184 }
23185 }
23186 }
23187
23188 true
23189 });
23190 self.gutter_highlights
23191 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23192 }
23193
23194 #[cfg(feature = "test-support")]
23195 pub fn all_text_highlights(
23196 &self,
23197 window: &mut Window,
23198 cx: &mut Context<Self>,
23199 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23200 let snapshot = self.snapshot(window, cx);
23201 self.display_map.update(cx, |display_map, _| {
23202 display_map
23203 .all_text_highlights()
23204 .map(|highlight| {
23205 let (style, ranges) = highlight.as_ref();
23206 (
23207 *style,
23208 ranges
23209 .iter()
23210 .map(|range| range.clone().to_display_points(&snapshot))
23211 .collect(),
23212 )
23213 })
23214 .collect()
23215 })
23216 }
23217
23218 #[cfg(feature = "test-support")]
23219 pub fn all_text_background_highlights(
23220 &self,
23221 window: &mut Window,
23222 cx: &mut Context<Self>,
23223 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23224 let snapshot = self.snapshot(window, cx);
23225 let buffer = &snapshot.buffer_snapshot();
23226 let start = buffer.anchor_before(MultiBufferOffset(0));
23227 let end = buffer.anchor_after(buffer.len());
23228 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23229 }
23230
23231 #[cfg(any(test, feature = "test-support"))]
23232 pub fn sorted_background_highlights_in_range(
23233 &self,
23234 search_range: Range<Anchor>,
23235 display_snapshot: &DisplaySnapshot,
23236 theme: &Theme,
23237 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23238 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23239 res.sort_by(|a, b| {
23240 a.0.start
23241 .cmp(&b.0.start)
23242 .then_with(|| a.0.end.cmp(&b.0.end))
23243 .then_with(|| a.1.cmp(&b.1))
23244 });
23245 res
23246 }
23247
23248 #[cfg(feature = "test-support")]
23249 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23250 let snapshot = self.buffer().read(cx).snapshot(cx);
23251
23252 let highlights = self
23253 .background_highlights
23254 .get(&HighlightKey::Type(TypeId::of::<
23255 items::BufferSearchHighlights,
23256 >()));
23257
23258 if let Some((_color, ranges)) = highlights {
23259 ranges
23260 .iter()
23261 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23262 .collect_vec()
23263 } else {
23264 vec![]
23265 }
23266 }
23267
23268 fn document_highlights_for_position<'a>(
23269 &'a self,
23270 position: Anchor,
23271 buffer: &'a MultiBufferSnapshot,
23272 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23273 let read_highlights = self
23274 .background_highlights
23275 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
23276 .map(|h| &h.1);
23277 let write_highlights = self
23278 .background_highlights
23279 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
23280 .map(|h| &h.1);
23281 let left_position = position.bias_left(buffer);
23282 let right_position = position.bias_right(buffer);
23283 read_highlights
23284 .into_iter()
23285 .chain(write_highlights)
23286 .flat_map(move |ranges| {
23287 let start_ix = match ranges.binary_search_by(|probe| {
23288 let cmp = probe.end.cmp(&left_position, buffer);
23289 if cmp.is_ge() {
23290 Ordering::Greater
23291 } else {
23292 Ordering::Less
23293 }
23294 }) {
23295 Ok(i) | Err(i) => i,
23296 };
23297
23298 ranges[start_ix..]
23299 .iter()
23300 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23301 })
23302 }
23303
23304 pub fn has_background_highlights<T: 'static>(&self) -> bool {
23305 self.background_highlights
23306 .get(&HighlightKey::Type(TypeId::of::<T>()))
23307 .is_some_and(|(_, highlights)| !highlights.is_empty())
23308 }
23309
23310 /// Returns all background highlights for a given range.
23311 ///
23312 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23313 pub fn background_highlights_in_range(
23314 &self,
23315 search_range: Range<Anchor>,
23316 display_snapshot: &DisplaySnapshot,
23317 theme: &Theme,
23318 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23319 let mut results = Vec::new();
23320 for (color_fetcher, ranges) in self.background_highlights.values() {
23321 let start_ix = match ranges.binary_search_by(|probe| {
23322 let cmp = probe
23323 .end
23324 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23325 if cmp.is_gt() {
23326 Ordering::Greater
23327 } else {
23328 Ordering::Less
23329 }
23330 }) {
23331 Ok(i) | Err(i) => i,
23332 };
23333 for (index, range) in ranges[start_ix..].iter().enumerate() {
23334 if range
23335 .start
23336 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23337 .is_ge()
23338 {
23339 break;
23340 }
23341
23342 let color = color_fetcher(&(start_ix + index), theme);
23343 let start = range.start.to_display_point(display_snapshot);
23344 let end = range.end.to_display_point(display_snapshot);
23345 results.push((start..end, color))
23346 }
23347 }
23348 results
23349 }
23350
23351 pub fn gutter_highlights_in_range(
23352 &self,
23353 search_range: Range<Anchor>,
23354 display_snapshot: &DisplaySnapshot,
23355 cx: &App,
23356 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23357 let mut results = Vec::new();
23358 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23359 let color = color_fetcher(cx);
23360 let start_ix = match ranges.binary_search_by(|probe| {
23361 let cmp = probe
23362 .end
23363 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23364 if cmp.is_gt() {
23365 Ordering::Greater
23366 } else {
23367 Ordering::Less
23368 }
23369 }) {
23370 Ok(i) | Err(i) => i,
23371 };
23372 for range in &ranges[start_ix..] {
23373 if range
23374 .start
23375 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23376 .is_ge()
23377 {
23378 break;
23379 }
23380
23381 let start = range.start.to_display_point(display_snapshot);
23382 let end = range.end.to_display_point(display_snapshot);
23383 results.push((start..end, color))
23384 }
23385 }
23386 results
23387 }
23388
23389 /// Get the text ranges corresponding to the redaction query
23390 pub fn redacted_ranges(
23391 &self,
23392 search_range: Range<Anchor>,
23393 display_snapshot: &DisplaySnapshot,
23394 cx: &App,
23395 ) -> Vec<Range<DisplayPoint>> {
23396 display_snapshot
23397 .buffer_snapshot()
23398 .redacted_ranges(search_range, |file| {
23399 if let Some(file) = file {
23400 file.is_private()
23401 && EditorSettings::get(
23402 Some(SettingsLocation {
23403 worktree_id: file.worktree_id(cx),
23404 path: file.path().as_ref(),
23405 }),
23406 cx,
23407 )
23408 .redact_private_values
23409 } else {
23410 false
23411 }
23412 })
23413 .map(|range| {
23414 range.start.to_display_point(display_snapshot)
23415 ..range.end.to_display_point(display_snapshot)
23416 })
23417 .collect()
23418 }
23419
23420 pub fn highlight_text_key<T: 'static>(
23421 &mut self,
23422 key: usize,
23423 ranges: Vec<Range<Anchor>>,
23424 style: HighlightStyle,
23425 merge: bool,
23426 cx: &mut Context<Self>,
23427 ) {
23428 self.display_map.update(cx, |map, cx| {
23429 map.highlight_text(
23430 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23431 ranges,
23432 style,
23433 merge,
23434 cx,
23435 );
23436 });
23437 cx.notify();
23438 }
23439
23440 pub fn highlight_text<T: 'static>(
23441 &mut self,
23442 ranges: Vec<Range<Anchor>>,
23443 style: HighlightStyle,
23444 cx: &mut Context<Self>,
23445 ) {
23446 self.display_map.update(cx, |map, cx| {
23447 map.highlight_text(
23448 HighlightKey::Type(TypeId::of::<T>()),
23449 ranges,
23450 style,
23451 false,
23452 cx,
23453 )
23454 });
23455 cx.notify();
23456 }
23457
23458 pub fn text_highlights<'a, T: 'static>(
23459 &'a self,
23460 cx: &'a App,
23461 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23462 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
23463 }
23464
23465 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
23466 let cleared = self
23467 .display_map
23468 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
23469 if cleared {
23470 cx.notify();
23471 }
23472 }
23473
23474 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23475 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23476 && self.focus_handle.is_focused(window)
23477 }
23478
23479 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23480 self.show_cursor_when_unfocused = is_enabled;
23481 cx.notify();
23482 }
23483
23484 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23485 cx.notify();
23486 }
23487
23488 fn on_debug_session_event(
23489 &mut self,
23490 _session: Entity<Session>,
23491 event: &SessionEvent,
23492 cx: &mut Context<Self>,
23493 ) {
23494 if let SessionEvent::InvalidateInlineValue = event {
23495 self.refresh_inline_values(cx);
23496 }
23497 }
23498
23499 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23500 let Some(project) = self.project.clone() else {
23501 return;
23502 };
23503
23504 if !self.inline_value_cache.enabled {
23505 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23506 self.splice_inlays(&inlays, Vec::new(), cx);
23507 return;
23508 }
23509
23510 let current_execution_position = self
23511 .highlighted_rows
23512 .get(&TypeId::of::<ActiveDebugLine>())
23513 .and_then(|lines| lines.last().map(|line| line.range.end));
23514
23515 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23516 let inline_values = editor
23517 .update(cx, |editor, cx| {
23518 let Some(current_execution_position) = current_execution_position else {
23519 return Some(Task::ready(Ok(Vec::new())));
23520 };
23521
23522 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23523 let snapshot = buffer.snapshot(cx);
23524
23525 let excerpt = snapshot.excerpt_containing(
23526 current_execution_position..current_execution_position,
23527 )?;
23528
23529 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23530 })?;
23531
23532 let range =
23533 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23534
23535 project.inline_values(buffer, range, cx)
23536 })
23537 .ok()
23538 .flatten()?
23539 .await
23540 .context("refreshing debugger inlays")
23541 .log_err()?;
23542
23543 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23544
23545 for (buffer_id, inline_value) in inline_values
23546 .into_iter()
23547 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23548 {
23549 buffer_inline_values
23550 .entry(buffer_id)
23551 .or_default()
23552 .push(inline_value);
23553 }
23554
23555 editor
23556 .update(cx, |editor, cx| {
23557 let snapshot = editor.buffer.read(cx).snapshot(cx);
23558 let mut new_inlays = Vec::default();
23559
23560 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23561 let buffer_id = buffer_snapshot.remote_id();
23562 buffer_inline_values
23563 .get(&buffer_id)
23564 .into_iter()
23565 .flatten()
23566 .for_each(|hint| {
23567 let inlay = Inlay::debugger(
23568 post_inc(&mut editor.next_inlay_id),
23569 Anchor::in_buffer(excerpt_id, hint.position),
23570 hint.text(),
23571 );
23572 if !inlay.text().chars().contains(&'\n') {
23573 new_inlays.push(inlay);
23574 }
23575 });
23576 }
23577
23578 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23579 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23580
23581 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23582 })
23583 .ok()?;
23584 Some(())
23585 });
23586 }
23587
23588 fn on_buffer_event(
23589 &mut self,
23590 multibuffer: &Entity<MultiBuffer>,
23591 event: &multi_buffer::Event,
23592 window: &mut Window,
23593 cx: &mut Context<Self>,
23594 ) {
23595 match event {
23596 multi_buffer::Event::Edited { edited_buffer } => {
23597 self.scrollbar_marker_state.dirty = true;
23598 self.active_indent_guides_state.dirty = true;
23599 self.refresh_active_diagnostics(cx);
23600 self.refresh_code_actions(window, cx);
23601 self.refresh_single_line_folds(window, cx);
23602 self.refresh_matching_bracket_highlights(window, cx);
23603 if self.has_active_edit_prediction() {
23604 self.update_visible_edit_prediction(window, cx);
23605 }
23606
23607 // Clean up orphaned review comments after edits
23608 self.cleanup_orphaned_review_comments(cx);
23609
23610 if let Some(buffer) = edited_buffer {
23611 if buffer.read(cx).file().is_none() {
23612 cx.emit(EditorEvent::TitleChanged);
23613 }
23614
23615 if self.project.is_some() {
23616 let buffer_id = buffer.read(cx).remote_id();
23617 self.register_buffer(buffer_id, cx);
23618 self.update_lsp_data(Some(buffer_id), window, cx);
23619 self.refresh_inlay_hints(
23620 InlayHintRefreshReason::BufferEdited(buffer_id),
23621 cx,
23622 );
23623 }
23624 }
23625
23626 cx.emit(EditorEvent::BufferEdited);
23627 cx.emit(SearchEvent::MatchesInvalidated);
23628
23629 let Some(project) = &self.project else { return };
23630 let (telemetry, is_via_ssh) = {
23631 let project = project.read(cx);
23632 let telemetry = project.client().telemetry().clone();
23633 let is_via_ssh = project.is_via_remote_server();
23634 (telemetry, is_via_ssh)
23635 };
23636 telemetry.log_edit_event("editor", is_via_ssh);
23637 }
23638 multi_buffer::Event::ExcerptsAdded {
23639 buffer,
23640 predecessor,
23641 excerpts,
23642 } => {
23643 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23644 let buffer_id = buffer.read(cx).remote_id();
23645 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23646 && let Some(project) = &self.project
23647 {
23648 update_uncommitted_diff_for_buffer(
23649 cx.entity(),
23650 project,
23651 [buffer.clone()],
23652 self.buffer.clone(),
23653 cx,
23654 )
23655 .detach();
23656 }
23657 self.update_lsp_data(Some(buffer_id), window, cx);
23658 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23659 self.colorize_brackets(false, cx);
23660 self.refresh_selected_text_highlights(true, window, cx);
23661 cx.emit(EditorEvent::ExcerptsAdded {
23662 buffer: buffer.clone(),
23663 predecessor: *predecessor,
23664 excerpts: excerpts.clone(),
23665 });
23666 }
23667 multi_buffer::Event::ExcerptsRemoved {
23668 ids,
23669 removed_buffer_ids,
23670 } => {
23671 if let Some(inlay_hints) = &mut self.inlay_hints {
23672 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23673 }
23674 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23675 for buffer_id in removed_buffer_ids {
23676 self.registered_buffers.remove(buffer_id);
23677 }
23678 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23679 cx.emit(EditorEvent::ExcerptsRemoved {
23680 ids: ids.clone(),
23681 removed_buffer_ids: removed_buffer_ids.clone(),
23682 });
23683 }
23684 multi_buffer::Event::ExcerptsEdited {
23685 excerpt_ids,
23686 buffer_ids,
23687 } => {
23688 self.display_map.update(cx, |map, cx| {
23689 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23690 });
23691 cx.emit(EditorEvent::ExcerptsEdited {
23692 ids: excerpt_ids.clone(),
23693 });
23694 }
23695 multi_buffer::Event::ExcerptsExpanded { ids } => {
23696 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23697 self.refresh_document_highlights(cx);
23698 for id in ids {
23699 self.fetched_tree_sitter_chunks.remove(id);
23700 }
23701 self.colorize_brackets(false, cx);
23702 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23703 }
23704 multi_buffer::Event::Reparsed(buffer_id) => {
23705 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23706 self.refresh_selected_text_highlights(true, window, cx);
23707 self.colorize_brackets(true, cx);
23708 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23709
23710 cx.emit(EditorEvent::Reparsed(*buffer_id));
23711 }
23712 multi_buffer::Event::DiffHunksToggled => {
23713 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23714 }
23715 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23716 if !is_fresh_language {
23717 self.registered_buffers.remove(&buffer_id);
23718 }
23719 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23720 cx.emit(EditorEvent::Reparsed(*buffer_id));
23721 cx.notify();
23722 }
23723 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
23724 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
23725 multi_buffer::Event::FileHandleChanged
23726 | multi_buffer::Event::Reloaded
23727 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
23728 multi_buffer::Event::DiagnosticsUpdated => {
23729 self.update_diagnostics_state(window, cx);
23730 }
23731 _ => {}
23732 };
23733 }
23734
23735 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
23736 if !self.diagnostics_enabled() {
23737 return;
23738 }
23739 self.refresh_active_diagnostics(cx);
23740 self.refresh_inline_diagnostics(true, window, cx);
23741 self.scrollbar_marker_state.dirty = true;
23742 cx.notify();
23743 }
23744
23745 pub fn start_temporary_diff_override(&mut self) {
23746 self.load_diff_task.take();
23747 self.temporary_diff_override = true;
23748 }
23749
23750 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
23751 self.temporary_diff_override = false;
23752 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
23753 self.buffer.update(cx, |buffer, cx| {
23754 buffer.set_all_diff_hunks_collapsed(cx);
23755 });
23756
23757 if let Some(project) = self.project.clone() {
23758 self.load_diff_task = Some(
23759 update_uncommitted_diff_for_buffer(
23760 cx.entity(),
23761 &project,
23762 self.buffer.read(cx).all_buffers(),
23763 self.buffer.clone(),
23764 cx,
23765 )
23766 .shared(),
23767 );
23768 }
23769 }
23770
23771 fn on_display_map_changed(
23772 &mut self,
23773 _: Entity<DisplayMap>,
23774 _: &mut Window,
23775 cx: &mut Context<Self>,
23776 ) {
23777 cx.notify();
23778 }
23779
23780 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
23781 if !self.mode.is_full() {
23782 return None;
23783 }
23784
23785 let theme_settings = theme::ThemeSettings::get_global(cx);
23786 let theme = cx.theme();
23787 let accent_colors = theme.accents().clone();
23788
23789 let accent_overrides = theme_settings
23790 .theme_overrides
23791 .get(theme.name.as_ref())
23792 .map(|theme_style| &theme_style.accents)
23793 .into_iter()
23794 .flatten()
23795 .chain(
23796 theme_settings
23797 .experimental_theme_overrides
23798 .as_ref()
23799 .map(|overrides| &overrides.accents)
23800 .into_iter()
23801 .flatten(),
23802 )
23803 .flat_map(|accent| accent.0.clone().map(SharedString::from))
23804 .collect();
23805
23806 Some(AccentData {
23807 colors: accent_colors,
23808 overrides: accent_overrides,
23809 })
23810 }
23811
23812 fn fetch_applicable_language_settings(
23813 &self,
23814 cx: &App,
23815 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
23816 if !self.mode.is_full() {
23817 return HashMap::default();
23818 }
23819
23820 self.buffer().read(cx).all_buffers().into_iter().fold(
23821 HashMap::default(),
23822 |mut acc, buffer| {
23823 let buffer = buffer.read(cx);
23824 let language = buffer.language().map(|language| language.name());
23825 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
23826 let file = buffer.file();
23827 v.insert(language_settings(language, file, cx).into_owned());
23828 }
23829 acc
23830 },
23831 )
23832 }
23833
23834 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23835 let new_language_settings = self.fetch_applicable_language_settings(cx);
23836 let language_settings_changed = new_language_settings != self.applicable_language_settings;
23837 self.applicable_language_settings = new_language_settings;
23838
23839 let new_accents = self.fetch_accent_data(cx);
23840 let accents_changed = new_accents != self.accent_data;
23841 self.accent_data = new_accents;
23842
23843 if self.diagnostics_enabled() {
23844 let new_severity = EditorSettings::get_global(cx)
23845 .diagnostics_max_severity
23846 .unwrap_or(DiagnosticSeverity::Hint);
23847 self.set_max_diagnostics_severity(new_severity, cx);
23848 }
23849 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23850 self.update_edit_prediction_settings(cx);
23851 self.refresh_edit_prediction(true, false, window, cx);
23852 self.refresh_inline_values(cx);
23853 self.refresh_inlay_hints(
23854 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
23855 self.selections.newest_anchor().head(),
23856 &self.buffer.read(cx).snapshot(cx),
23857 cx,
23858 )),
23859 cx,
23860 );
23861
23862 let old_cursor_shape = self.cursor_shape;
23863 let old_show_breadcrumbs = self.show_breadcrumbs;
23864
23865 {
23866 let editor_settings = EditorSettings::get_global(cx);
23867 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
23868 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
23869 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
23870 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
23871 }
23872
23873 if old_cursor_shape != self.cursor_shape {
23874 cx.emit(EditorEvent::CursorShapeChanged);
23875 }
23876
23877 if old_show_breadcrumbs != self.show_breadcrumbs {
23878 cx.emit(EditorEvent::BreadcrumbsChanged);
23879 }
23880
23881 let project_settings = ProjectSettings::get_global(cx);
23882 self.buffer_serialization = self
23883 .should_serialize_buffer()
23884 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
23885
23886 if self.mode.is_full() {
23887 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
23888 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
23889 if self.show_inline_diagnostics != show_inline_diagnostics {
23890 self.show_inline_diagnostics = show_inline_diagnostics;
23891 self.refresh_inline_diagnostics(false, window, cx);
23892 }
23893
23894 if self.git_blame_inline_enabled != inline_blame_enabled {
23895 self.toggle_git_blame_inline_internal(false, window, cx);
23896 }
23897
23898 let minimap_settings = EditorSettings::get_global(cx).minimap;
23899 if self.minimap_visibility != MinimapVisibility::Disabled {
23900 if self.minimap_visibility.settings_visibility()
23901 != minimap_settings.minimap_enabled()
23902 {
23903 self.set_minimap_visibility(
23904 MinimapVisibility::for_mode(self.mode(), cx),
23905 window,
23906 cx,
23907 );
23908 } else if let Some(minimap_entity) = self.minimap.as_ref() {
23909 minimap_entity.update(cx, |minimap_editor, cx| {
23910 minimap_editor.update_minimap_configuration(minimap_settings, cx)
23911 })
23912 }
23913 }
23914
23915 if language_settings_changed || accents_changed {
23916 self.colorize_brackets(true, cx);
23917 }
23918
23919 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
23920 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
23921 }) {
23922 if !inlay_splice.is_empty() {
23923 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
23924 }
23925 self.refresh_colors_for_visible_range(None, window, cx);
23926 }
23927 }
23928
23929 cx.notify();
23930 }
23931
23932 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23933 if !self.mode.is_full() {
23934 return;
23935 }
23936
23937 let new_accents = self.fetch_accent_data(cx);
23938 if new_accents != self.accent_data {
23939 self.accent_data = new_accents;
23940 self.colorize_brackets(true, cx);
23941 }
23942 }
23943
23944 pub fn set_searchable(&mut self, searchable: bool) {
23945 self.searchable = searchable;
23946 }
23947
23948 pub fn searchable(&self) -> bool {
23949 self.searchable
23950 }
23951
23952 pub fn open_excerpts_in_split(
23953 &mut self,
23954 _: &OpenExcerptsSplit,
23955 window: &mut Window,
23956 cx: &mut Context<Self>,
23957 ) {
23958 self.open_excerpts_common(None, true, window, cx)
23959 }
23960
23961 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
23962 self.open_excerpts_common(None, false, window, cx)
23963 }
23964
23965 pub(crate) fn open_excerpts_common(
23966 &mut self,
23967 jump_data: Option<JumpData>,
23968 split: bool,
23969 window: &mut Window,
23970 cx: &mut Context<Self>,
23971 ) {
23972 let Some(workspace) = self.workspace() else {
23973 cx.propagate();
23974 return;
23975 };
23976
23977 if self.buffer.read(cx).is_singleton() {
23978 cx.propagate();
23979 return;
23980 }
23981
23982 let mut new_selections_by_buffer = HashMap::default();
23983 match &jump_data {
23984 Some(JumpData::MultiBufferPoint {
23985 excerpt_id,
23986 position,
23987 anchor,
23988 line_offset_from_top,
23989 }) => {
23990 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
23991 if let Some(buffer) = multi_buffer_snapshot
23992 .buffer_id_for_excerpt(*excerpt_id)
23993 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
23994 {
23995 let buffer_snapshot = buffer.read(cx).snapshot();
23996 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
23997 language::ToPoint::to_point(anchor, &buffer_snapshot)
23998 } else {
23999 buffer_snapshot.clip_point(*position, Bias::Left)
24000 };
24001 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24002 new_selections_by_buffer.insert(
24003 buffer,
24004 (
24005 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24006 Some(*line_offset_from_top),
24007 ),
24008 );
24009 }
24010 }
24011 Some(JumpData::MultiBufferRow {
24012 row,
24013 line_offset_from_top,
24014 }) => {
24015 let point = MultiBufferPoint::new(row.0, 0);
24016 if let Some((buffer, buffer_point, _)) =
24017 self.buffer.read(cx).point_to_buffer_point(point, cx)
24018 {
24019 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24020 new_selections_by_buffer
24021 .entry(buffer)
24022 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24023 .0
24024 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24025 }
24026 }
24027 None => {
24028 let selections = self
24029 .selections
24030 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24031 let multi_buffer = self.buffer.read(cx);
24032 for selection in selections {
24033 for (snapshot, range, _, anchor) in multi_buffer
24034 .snapshot(cx)
24035 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24036 {
24037 if let Some(anchor) = anchor {
24038 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24039 else {
24040 continue;
24041 };
24042 let offset = text::ToOffset::to_offset(
24043 &anchor.text_anchor,
24044 &buffer_handle.read(cx).snapshot(),
24045 );
24046 let range = BufferOffset(offset)..BufferOffset(offset);
24047 new_selections_by_buffer
24048 .entry(buffer_handle)
24049 .or_insert((Vec::new(), None))
24050 .0
24051 .push(range)
24052 } else {
24053 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24054 else {
24055 continue;
24056 };
24057 new_selections_by_buffer
24058 .entry(buffer_handle)
24059 .or_insert((Vec::new(), None))
24060 .0
24061 .push(range)
24062 }
24063 }
24064 }
24065 }
24066 }
24067
24068 new_selections_by_buffer
24069 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24070
24071 if new_selections_by_buffer.is_empty() {
24072 return;
24073 }
24074
24075 // We defer the pane interaction because we ourselves are a workspace item
24076 // and activating a new item causes the pane to call a method on us reentrantly,
24077 // which panics if we're on the stack.
24078 window.defer(cx, move |window, cx| {
24079 workspace.update(cx, |workspace, cx| {
24080 let pane = if split {
24081 workspace.adjacent_pane(window, cx)
24082 } else {
24083 workspace.active_pane().clone()
24084 };
24085
24086 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24087 let buffer_read = buffer.read(cx);
24088 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24089 (true, project::File::from_dyn(Some(file)).is_some())
24090 } else {
24091 (false, false)
24092 };
24093
24094 // If project file is none workspace.open_project_item will fail to open the excerpt
24095 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24096 // so we check if there's a tab match in that case first
24097 let editor = (!has_file || !is_project_file)
24098 .then(|| {
24099 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24100 // so `workspace.open_project_item` will never find them, always opening a new editor.
24101 // Instead, we try to activate the existing editor in the pane first.
24102 let (editor, pane_item_index, pane_item_id) =
24103 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24104 let editor = item.downcast::<Editor>()?;
24105 let singleton_buffer =
24106 editor.read(cx).buffer().read(cx).as_singleton()?;
24107 if singleton_buffer == buffer {
24108 Some((editor, i, item.item_id()))
24109 } else {
24110 None
24111 }
24112 })?;
24113 pane.update(cx, |pane, cx| {
24114 pane.activate_item(pane_item_index, true, true, window, cx);
24115 if !PreviewTabsSettings::get_global(cx)
24116 .enable_preview_from_multibuffer
24117 {
24118 pane.unpreview_item_if_preview(pane_item_id);
24119 }
24120 });
24121 Some(editor)
24122 })
24123 .flatten()
24124 .unwrap_or_else(|| {
24125 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24126 .enable_keep_preview_on_code_navigation;
24127 let allow_new_preview =
24128 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
24129 workspace.open_project_item::<Self>(
24130 pane.clone(),
24131 buffer,
24132 true,
24133 true,
24134 keep_old_preview,
24135 allow_new_preview,
24136 window,
24137 cx,
24138 )
24139 });
24140
24141 editor.update(cx, |editor, cx| {
24142 if has_file && !is_project_file {
24143 editor.set_read_only(true);
24144 }
24145 let autoscroll = match scroll_offset {
24146 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
24147 None => Autoscroll::newest(),
24148 };
24149 let nav_history = editor.nav_history.take();
24150 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24151 let Some((&excerpt_id, _, buffer_snapshot)) =
24152 multibuffer_snapshot.as_singleton()
24153 else {
24154 return;
24155 };
24156 editor.change_selections(
24157 SelectionEffects::scroll(autoscroll),
24158 window,
24159 cx,
24160 |s| {
24161 s.select_ranges(ranges.into_iter().map(|range| {
24162 let range = buffer_snapshot.anchor_before(range.start)
24163 ..buffer_snapshot.anchor_after(range.end);
24164 multibuffer_snapshot
24165 .anchor_range_in_excerpt(excerpt_id, range)
24166 .unwrap()
24167 }));
24168 },
24169 );
24170 editor.nav_history = nav_history;
24171 });
24172 }
24173 })
24174 });
24175 }
24176
24177 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24178 let snapshot = self.buffer.read(cx).read(cx);
24179 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
24180 Some(
24181 ranges
24182 .iter()
24183 .map(move |range| {
24184 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24185 })
24186 .collect(),
24187 )
24188 }
24189
24190 fn selection_replacement_ranges(
24191 &self,
24192 range: Range<MultiBufferOffsetUtf16>,
24193 cx: &mut App,
24194 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24195 let selections = self
24196 .selections
24197 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24198 let newest_selection = selections
24199 .iter()
24200 .max_by_key(|selection| selection.id)
24201 .unwrap();
24202 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24203 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24204 let snapshot = self.buffer.read(cx).read(cx);
24205 selections
24206 .into_iter()
24207 .map(|mut selection| {
24208 selection.start.0.0 =
24209 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24210 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24211 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24212 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24213 })
24214 .collect()
24215 }
24216
24217 fn report_editor_event(
24218 &self,
24219 reported_event: ReportEditorEvent,
24220 file_extension: Option<String>,
24221 cx: &App,
24222 ) {
24223 if cfg!(any(test, feature = "test-support")) {
24224 return;
24225 }
24226
24227 let Some(project) = &self.project else { return };
24228
24229 // If None, we are in a file without an extension
24230 let file = self
24231 .buffer
24232 .read(cx)
24233 .as_singleton()
24234 .and_then(|b| b.read(cx).file());
24235 let file_extension = file_extension.or(file
24236 .as_ref()
24237 .and_then(|file| Path::new(file.file_name(cx)).extension())
24238 .and_then(|e| e.to_str())
24239 .map(|a| a.to_string()));
24240
24241 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24242 .map(|vim_mode| vim_mode.0)
24243 .unwrap_or(false);
24244
24245 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24246 let copilot_enabled = edit_predictions_provider
24247 == language::language_settings::EditPredictionProvider::Copilot;
24248 let copilot_enabled_for_language = self
24249 .buffer
24250 .read(cx)
24251 .language_settings(cx)
24252 .show_edit_predictions;
24253
24254 let project = project.read(cx);
24255 let event_type = reported_event.event_type();
24256
24257 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24258 telemetry::event!(
24259 event_type,
24260 type = if auto_saved {"autosave"} else {"manual"},
24261 file_extension,
24262 vim_mode,
24263 copilot_enabled,
24264 copilot_enabled_for_language,
24265 edit_predictions_provider,
24266 is_via_ssh = project.is_via_remote_server(),
24267 );
24268 } else {
24269 telemetry::event!(
24270 event_type,
24271 file_extension,
24272 vim_mode,
24273 copilot_enabled,
24274 copilot_enabled_for_language,
24275 edit_predictions_provider,
24276 is_via_ssh = project.is_via_remote_server(),
24277 );
24278 };
24279 }
24280
24281 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24282 /// with each line being an array of {text, highlight} objects.
24283 fn copy_highlight_json(
24284 &mut self,
24285 _: &CopyHighlightJson,
24286 window: &mut Window,
24287 cx: &mut Context<Self>,
24288 ) {
24289 #[derive(Serialize)]
24290 struct Chunk<'a> {
24291 text: String,
24292 highlight: Option<&'a str>,
24293 }
24294
24295 let snapshot = self.buffer.read(cx).snapshot(cx);
24296 let range = self
24297 .selected_text_range(false, window, cx)
24298 .and_then(|selection| {
24299 if selection.range.is_empty() {
24300 None
24301 } else {
24302 Some(
24303 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24304 selection.range.start,
24305 )))
24306 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24307 selection.range.end,
24308 ))),
24309 )
24310 }
24311 })
24312 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24313
24314 let chunks = snapshot.chunks(range, true);
24315 let mut lines = Vec::new();
24316 let mut line: VecDeque<Chunk> = VecDeque::new();
24317
24318 let Some(style) = self.style.as_ref() else {
24319 return;
24320 };
24321
24322 for chunk in chunks {
24323 let highlight = chunk
24324 .syntax_highlight_id
24325 .and_then(|id| id.name(&style.syntax));
24326 let mut chunk_lines = chunk.text.split('\n').peekable();
24327 while let Some(text) = chunk_lines.next() {
24328 let mut merged_with_last_token = false;
24329 if let Some(last_token) = line.back_mut()
24330 && last_token.highlight == highlight
24331 {
24332 last_token.text.push_str(text);
24333 merged_with_last_token = true;
24334 }
24335
24336 if !merged_with_last_token {
24337 line.push_back(Chunk {
24338 text: text.into(),
24339 highlight,
24340 });
24341 }
24342
24343 if chunk_lines.peek().is_some() {
24344 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24345 line.pop_front();
24346 }
24347 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24348 line.pop_back();
24349 }
24350
24351 lines.push(mem::take(&mut line));
24352 }
24353 }
24354 }
24355
24356 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24357 return;
24358 };
24359 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24360 }
24361
24362 pub fn open_context_menu(
24363 &mut self,
24364 _: &OpenContextMenu,
24365 window: &mut Window,
24366 cx: &mut Context<Self>,
24367 ) {
24368 self.request_autoscroll(Autoscroll::newest(), cx);
24369 let position = self
24370 .selections
24371 .newest_display(&self.display_snapshot(cx))
24372 .start;
24373 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24374 }
24375
24376 pub fn replay_insert_event(
24377 &mut self,
24378 text: &str,
24379 relative_utf16_range: Option<Range<isize>>,
24380 window: &mut Window,
24381 cx: &mut Context<Self>,
24382 ) {
24383 if !self.input_enabled {
24384 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24385 return;
24386 }
24387 if let Some(relative_utf16_range) = relative_utf16_range {
24388 let selections = self
24389 .selections
24390 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24391 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24392 let new_ranges = selections.into_iter().map(|range| {
24393 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24394 range
24395 .head()
24396 .0
24397 .0
24398 .saturating_add_signed(relative_utf16_range.start),
24399 ));
24400 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24401 range
24402 .head()
24403 .0
24404 .0
24405 .saturating_add_signed(relative_utf16_range.end),
24406 ));
24407 start..end
24408 });
24409 s.select_ranges(new_ranges);
24410 });
24411 }
24412
24413 self.handle_input(text, window, cx);
24414 }
24415
24416 pub fn is_focused(&self, window: &Window) -> bool {
24417 self.focus_handle.is_focused(window)
24418 }
24419
24420 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24421 cx.emit(EditorEvent::Focused);
24422
24423 if let Some(descendant) = self
24424 .last_focused_descendant
24425 .take()
24426 .and_then(|descendant| descendant.upgrade())
24427 {
24428 window.focus(&descendant, cx);
24429 } else {
24430 if let Some(blame) = self.blame.as_ref() {
24431 blame.update(cx, GitBlame::focus)
24432 }
24433
24434 self.blink_manager.update(cx, BlinkManager::enable);
24435 self.show_cursor_names(window, cx);
24436 self.buffer.update(cx, |buffer, cx| {
24437 buffer.finalize_last_transaction(cx);
24438 if self.leader_id.is_none() {
24439 buffer.set_active_selections(
24440 &self.selections.disjoint_anchors_arc(),
24441 self.selections.line_mode(),
24442 self.cursor_shape,
24443 cx,
24444 );
24445 }
24446 });
24447
24448 if let Some(position_map) = self.last_position_map.clone() {
24449 EditorElement::mouse_moved(
24450 self,
24451 &MouseMoveEvent {
24452 position: window.mouse_position(),
24453 pressed_button: None,
24454 modifiers: window.modifiers(),
24455 },
24456 &position_map,
24457 None,
24458 window,
24459 cx,
24460 );
24461 }
24462 }
24463 }
24464
24465 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24466 cx.emit(EditorEvent::FocusedIn)
24467 }
24468
24469 fn handle_focus_out(
24470 &mut self,
24471 event: FocusOutEvent,
24472 _window: &mut Window,
24473 cx: &mut Context<Self>,
24474 ) {
24475 if event.blurred != self.focus_handle {
24476 self.last_focused_descendant = Some(event.blurred);
24477 }
24478 self.selection_drag_state = SelectionDragState::None;
24479 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24480 }
24481
24482 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24483 self.blink_manager.update(cx, BlinkManager::disable);
24484 self.buffer
24485 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24486
24487 if let Some(blame) = self.blame.as_ref() {
24488 blame.update(cx, GitBlame::blur)
24489 }
24490 if !self.hover_state.focused(window, cx) {
24491 hide_hover(self, cx);
24492 }
24493 if !self
24494 .context_menu
24495 .borrow()
24496 .as_ref()
24497 .is_some_and(|context_menu| context_menu.focused(window, cx))
24498 {
24499 self.hide_context_menu(window, cx);
24500 }
24501 self.take_active_edit_prediction(cx);
24502 cx.emit(EditorEvent::Blurred);
24503 cx.notify();
24504 }
24505
24506 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24507 let mut pending: String = window
24508 .pending_input_keystrokes()
24509 .into_iter()
24510 .flatten()
24511 .filter_map(|keystroke| keystroke.key_char.clone())
24512 .collect();
24513
24514 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24515 pending = "".to_string();
24516 }
24517
24518 let existing_pending = self
24519 .text_highlights::<PendingInput>(cx)
24520 .map(|(_, ranges)| ranges.to_vec());
24521 if existing_pending.is_none() && pending.is_empty() {
24522 return;
24523 }
24524 let transaction =
24525 self.transact(window, cx, |this, window, cx| {
24526 let selections = this
24527 .selections
24528 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24529 let edits = selections
24530 .iter()
24531 .map(|selection| (selection.end..selection.end, pending.clone()));
24532 this.edit(edits, cx);
24533 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24534 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24535 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24536 }));
24537 });
24538 if let Some(existing_ranges) = existing_pending {
24539 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24540 this.edit(edits, cx);
24541 }
24542 });
24543
24544 let snapshot = self.snapshot(window, cx);
24545 let ranges = self
24546 .selections
24547 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24548 .into_iter()
24549 .map(|selection| {
24550 snapshot.buffer_snapshot().anchor_after(selection.end)
24551 ..snapshot
24552 .buffer_snapshot()
24553 .anchor_before(selection.end + pending.len())
24554 })
24555 .collect();
24556
24557 if pending.is_empty() {
24558 self.clear_highlights::<PendingInput>(cx);
24559 } else {
24560 self.highlight_text::<PendingInput>(
24561 ranges,
24562 HighlightStyle {
24563 underline: Some(UnderlineStyle {
24564 thickness: px(1.),
24565 color: None,
24566 wavy: false,
24567 }),
24568 ..Default::default()
24569 },
24570 cx,
24571 );
24572 }
24573
24574 self.ime_transaction = self.ime_transaction.or(transaction);
24575 if let Some(transaction) = self.ime_transaction {
24576 self.buffer.update(cx, |buffer, cx| {
24577 buffer.group_until_transaction(transaction, cx);
24578 });
24579 }
24580
24581 if self.text_highlights::<PendingInput>(cx).is_none() {
24582 self.ime_transaction.take();
24583 }
24584 }
24585
24586 pub fn register_action_renderer(
24587 &mut self,
24588 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24589 ) -> Subscription {
24590 let id = self.next_editor_action_id.post_inc();
24591 self.editor_actions
24592 .borrow_mut()
24593 .insert(id, Box::new(listener));
24594
24595 let editor_actions = self.editor_actions.clone();
24596 Subscription::new(move || {
24597 editor_actions.borrow_mut().remove(&id);
24598 })
24599 }
24600
24601 pub fn register_action<A: Action>(
24602 &mut self,
24603 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24604 ) -> Subscription {
24605 let id = self.next_editor_action_id.post_inc();
24606 let listener = Arc::new(listener);
24607 self.editor_actions.borrow_mut().insert(
24608 id,
24609 Box::new(move |_, window, _| {
24610 let listener = listener.clone();
24611 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24612 let action = action.downcast_ref().unwrap();
24613 if phase == DispatchPhase::Bubble {
24614 listener(action, window, cx)
24615 }
24616 })
24617 }),
24618 );
24619
24620 let editor_actions = self.editor_actions.clone();
24621 Subscription::new(move || {
24622 editor_actions.borrow_mut().remove(&id);
24623 })
24624 }
24625
24626 pub fn file_header_size(&self) -> u32 {
24627 FILE_HEADER_HEIGHT
24628 }
24629
24630 pub fn restore(
24631 &mut self,
24632 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24633 window: &mut Window,
24634 cx: &mut Context<Self>,
24635 ) {
24636 self.buffer().update(cx, |multi_buffer, cx| {
24637 for (buffer_id, changes) in revert_changes {
24638 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24639 buffer.update(cx, |buffer, cx| {
24640 buffer.edit(
24641 changes
24642 .into_iter()
24643 .map(|(range, text)| (range, text.to_string())),
24644 None,
24645 cx,
24646 );
24647 });
24648 }
24649 }
24650 });
24651 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24652 selections.refresh()
24653 });
24654 }
24655
24656 pub fn to_pixel_point(
24657 &mut self,
24658 source: multi_buffer::Anchor,
24659 editor_snapshot: &EditorSnapshot,
24660 window: &mut Window,
24661 cx: &App,
24662 ) -> Option<gpui::Point<Pixels>> {
24663 let source_point = source.to_display_point(editor_snapshot);
24664 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
24665 }
24666
24667 pub fn display_to_pixel_point(
24668 &mut self,
24669 source: DisplayPoint,
24670 editor_snapshot: &EditorSnapshot,
24671 window: &mut Window,
24672 cx: &App,
24673 ) -> Option<gpui::Point<Pixels>> {
24674 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
24675 let text_layout_details = self.text_layout_details(window);
24676 let scroll_top = text_layout_details
24677 .scroll_anchor
24678 .scroll_position(editor_snapshot)
24679 .y;
24680
24681 if source.row().as_f64() < scroll_top.floor() {
24682 return None;
24683 }
24684 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
24685 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
24686 Some(gpui::Point::new(source_x, source_y))
24687 }
24688
24689 pub fn has_visible_completions_menu(&self) -> bool {
24690 !self.edit_prediction_preview_is_active()
24691 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
24692 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
24693 })
24694 }
24695
24696 pub fn register_addon<T: Addon>(&mut self, instance: T) {
24697 if self.mode.is_minimap() {
24698 return;
24699 }
24700 self.addons
24701 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
24702 }
24703
24704 pub fn unregister_addon<T: Addon>(&mut self) {
24705 self.addons.remove(&std::any::TypeId::of::<T>());
24706 }
24707
24708 pub fn addon<T: Addon>(&self) -> Option<&T> {
24709 let type_id = std::any::TypeId::of::<T>();
24710 self.addons
24711 .get(&type_id)
24712 .and_then(|item| item.to_any().downcast_ref::<T>())
24713 }
24714
24715 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
24716 let type_id = std::any::TypeId::of::<T>();
24717 self.addons
24718 .get_mut(&type_id)
24719 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
24720 }
24721
24722 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
24723 let text_layout_details = self.text_layout_details(window);
24724 let style = &text_layout_details.editor_style;
24725 let font_id = window.text_system().resolve_font(&style.text.font());
24726 let font_size = style.text.font_size.to_pixels(window.rem_size());
24727 let line_height = style.text.line_height_in_pixels(window.rem_size());
24728 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
24729 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
24730
24731 CharacterDimensions {
24732 em_width,
24733 em_advance,
24734 line_height,
24735 }
24736 }
24737
24738 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
24739 self.load_diff_task.clone()
24740 }
24741
24742 fn read_metadata_from_db(
24743 &mut self,
24744 item_id: u64,
24745 workspace_id: WorkspaceId,
24746 window: &mut Window,
24747 cx: &mut Context<Editor>,
24748 ) {
24749 if self.buffer_kind(cx) == ItemBufferKind::Singleton
24750 && !self.mode.is_minimap()
24751 && WorkspaceSettings::get(None, cx).restore_on_startup
24752 != RestoreOnStartupBehavior::EmptyTab
24753 {
24754 let buffer_snapshot = OnceCell::new();
24755
24756 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
24757 && !folds.is_empty()
24758 {
24759 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24760 let snapshot_len = snapshot.len().0;
24761
24762 // Helper: search for fingerprint in buffer, return offset if found
24763 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
24764 // Ensure we start at a character boundary (defensive)
24765 let search_start = snapshot
24766 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
24767 .0;
24768 let search_end = snapshot_len.saturating_sub(fingerprint.len());
24769
24770 let mut byte_offset = search_start;
24771 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
24772 if byte_offset > search_end {
24773 break;
24774 }
24775 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
24776 return Some(byte_offset);
24777 }
24778 byte_offset += ch.len_utf8();
24779 }
24780 None
24781 };
24782
24783 // Track search position to handle duplicate fingerprints correctly.
24784 // Folds are stored in document order, so we advance after each match.
24785 let mut search_start = 0usize;
24786
24787 let valid_folds: Vec<_> = folds
24788 .into_iter()
24789 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
24790 // Skip folds without fingerprints (old data before migration)
24791 let sfp = start_fp?;
24792 let efp = end_fp?;
24793 let efp_len = efp.len();
24794
24795 // Fast path: check if fingerprints match at stored offsets
24796 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
24797 let start_matches = stored_start < snapshot_len
24798 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
24799 let efp_check_pos = stored_end.saturating_sub(efp_len);
24800 let end_matches = efp_check_pos >= stored_start
24801 && stored_end <= snapshot_len
24802 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
24803
24804 let (new_start, new_end) = if start_matches && end_matches {
24805 // Offsets unchanged, use stored values
24806 (stored_start, stored_end)
24807 } else if sfp == efp {
24808 // Short fold: identical fingerprints can only match once per search
24809 // Use stored fold length to compute new_end
24810 let new_start = find_fingerprint(&sfp, search_start)?;
24811 let fold_len = stored_end - stored_start;
24812 let new_end = new_start + fold_len;
24813 (new_start, new_end)
24814 } else {
24815 // Slow path: search for fingerprints in buffer
24816 let new_start = find_fingerprint(&sfp, search_start)?;
24817 // Search for end_fp after start, then add efp_len to get actual fold end
24818 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
24819 let new_end = efp_pos + efp_len;
24820 (new_start, new_end)
24821 };
24822
24823 // Advance search position for next fold
24824 search_start = new_end;
24825
24826 // Validate fold makes sense (end must be after start)
24827 if new_end <= new_start {
24828 return None;
24829 }
24830
24831 Some(
24832 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
24833 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
24834 )
24835 })
24836 .collect();
24837
24838 if !valid_folds.is_empty() {
24839 self.fold_ranges(valid_folds, false, window, cx);
24840
24841 // Migrate folds to current entity_id before workspace cleanup runs.
24842 // Entity IDs change between sessions, but workspace cleanup deletes
24843 // old editor rows (cascading to folds) based on current entity IDs.
24844 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
24845 if new_editor_id != item_id {
24846 cx.spawn(async move |_, _| {
24847 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
24848 .await
24849 .log_err();
24850 })
24851 .detach();
24852 }
24853 }
24854 }
24855
24856 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
24857 && !selections.is_empty()
24858 {
24859 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24860 // skip adding the initial selection to selection history
24861 self.selection_history.mode = SelectionHistoryMode::Skipping;
24862 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24863 s.select_ranges(selections.into_iter().map(|(start, end)| {
24864 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
24865 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
24866 }));
24867 });
24868 self.selection_history.mode = SelectionHistoryMode::Normal;
24869 };
24870 }
24871
24872 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
24873 }
24874
24875 fn update_lsp_data(
24876 &mut self,
24877 for_buffer: Option<BufferId>,
24878 window: &mut Window,
24879 cx: &mut Context<'_, Self>,
24880 ) {
24881 if let Some(buffer_id) = for_buffer {
24882 self.pull_diagnostics(buffer_id, window, cx);
24883 }
24884 self.refresh_colors_for_visible_range(for_buffer, window, cx);
24885 }
24886
24887 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
24888 if self.ignore_lsp_data() {
24889 return;
24890 }
24891 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
24892 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
24893 }
24894 }
24895
24896 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
24897 if self.ignore_lsp_data() {
24898 return;
24899 }
24900
24901 if !self.registered_buffers.contains_key(&buffer_id)
24902 && let Some(project) = self.project.as_ref()
24903 {
24904 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
24905 project.update(cx, |project, cx| {
24906 self.registered_buffers.insert(
24907 buffer_id,
24908 project.register_buffer_with_language_servers(&buffer, cx),
24909 );
24910 });
24911 } else {
24912 self.registered_buffers.remove(&buffer_id);
24913 }
24914 }
24915 }
24916
24917 fn ignore_lsp_data(&self) -> bool {
24918 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
24919 // skip any LSP updates for it.
24920 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
24921 }
24922
24923 pub(crate) fn create_style(&self, cx: &App) -> EditorStyle {
24924 let settings = ThemeSettings::get_global(cx);
24925
24926 let mut text_style = match self.mode {
24927 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24928 color: cx.theme().colors().editor_foreground,
24929 font_family: settings.ui_font.family.clone(),
24930 font_features: settings.ui_font.features.clone(),
24931 font_fallbacks: settings.ui_font.fallbacks.clone(),
24932 font_size: rems(0.875).into(),
24933 font_weight: settings.ui_font.weight,
24934 line_height: relative(settings.buffer_line_height.value()),
24935 ..Default::default()
24936 },
24937 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24938 color: cx.theme().colors().editor_foreground,
24939 font_family: settings.buffer_font.family.clone(),
24940 font_features: settings.buffer_font.features.clone(),
24941 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24942 font_size: settings.buffer_font_size(cx).into(),
24943 font_weight: settings.buffer_font.weight,
24944 line_height: relative(settings.buffer_line_height.value()),
24945 ..Default::default()
24946 },
24947 };
24948 if let Some(text_style_refinement) = &self.text_style_refinement {
24949 text_style.refine(text_style_refinement)
24950 }
24951
24952 let background = match self.mode {
24953 EditorMode::SingleLine => cx.theme().system().transparent,
24954 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24955 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24956 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24957 };
24958
24959 EditorStyle {
24960 background,
24961 border: cx.theme().colors().border,
24962 local_player: cx.theme().players().local(),
24963 text: text_style,
24964 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24965 syntax: cx.theme().syntax().clone(),
24966 status: cx.theme().status().clone(),
24967 inlay_hints_style: make_inlay_hints_style(cx),
24968 edit_prediction_styles: make_suggestion_styles(cx),
24969 unnecessary_code_fade: settings.unnecessary_code_fade,
24970 show_underlines: self.diagnostics_enabled(),
24971 }
24972 }
24973 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
24974 let cursor = self.selections.newest_anchor().head();
24975 let multibuffer = self.buffer().read(cx);
24976 let is_singleton = multibuffer.is_singleton();
24977 let (buffer_id, symbols) = multibuffer
24978 .read(cx)
24979 .symbols_containing(cursor, Some(variant.syntax()))?;
24980 let buffer = multibuffer.buffer(buffer_id)?;
24981
24982 let buffer = buffer.read(cx);
24983 let settings = ThemeSettings::get_global(cx);
24984 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
24985 let mut breadcrumbs = if is_singleton {
24986 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
24987 buffer
24988 .snapshot()
24989 .resolve_file_path(
24990 self.project
24991 .as_ref()
24992 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
24993 .unwrap_or_default(),
24994 cx,
24995 )
24996 .unwrap_or_else(|| {
24997 if multibuffer.is_singleton() {
24998 multibuffer.title(cx).to_string()
24999 } else {
25000 "untitled".to_string()
25001 }
25002 })
25003 });
25004 vec![BreadcrumbText {
25005 text,
25006 highlights: None,
25007 font: Some(settings.buffer_font.clone()),
25008 }]
25009 } else {
25010 vec![]
25011 };
25012
25013 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
25014 text: symbol.text,
25015 highlights: Some(symbol.highlight_ranges),
25016 font: Some(settings.buffer_font.clone()),
25017 }));
25018 Some(breadcrumbs)
25019 }
25020}
25021
25022fn edit_for_markdown_paste<'a>(
25023 buffer: &MultiBufferSnapshot,
25024 range: Range<MultiBufferOffset>,
25025 to_insert: &'a str,
25026 url: Option<url::Url>,
25027) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25028 if url.is_none() {
25029 return (range, Cow::Borrowed(to_insert));
25030 };
25031
25032 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25033
25034 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25035 Cow::Borrowed(to_insert)
25036 } else {
25037 Cow::Owned(format!("[{old_text}]({to_insert})"))
25038 };
25039 (range, new_text)
25040}
25041
25042fn process_completion_for_edit(
25043 completion: &Completion,
25044 intent: CompletionIntent,
25045 buffer: &Entity<Buffer>,
25046 cursor_position: &text::Anchor,
25047 cx: &mut Context<Editor>,
25048) -> CompletionEdit {
25049 let buffer = buffer.read(cx);
25050 let buffer_snapshot = buffer.snapshot();
25051 let (snippet, new_text) = if completion.is_snippet() {
25052 let mut snippet_source = completion.new_text.clone();
25053 // Workaround for typescript language server issues so that methods don't expand within
25054 // strings and functions with type expressions. The previous point is used because the query
25055 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25056 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25057 let previous_point = if previous_point.column > 0 {
25058 cursor_position.to_previous_offset(&buffer_snapshot)
25059 } else {
25060 cursor_position.to_offset(&buffer_snapshot)
25061 };
25062 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25063 && scope.prefers_label_for_snippet_in_completion()
25064 && let Some(label) = completion.label()
25065 && matches!(
25066 completion.kind(),
25067 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25068 )
25069 {
25070 snippet_source = label;
25071 }
25072 match Snippet::parse(&snippet_source).log_err() {
25073 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25074 None => (None, completion.new_text.clone()),
25075 }
25076 } else {
25077 (None, completion.new_text.clone())
25078 };
25079
25080 let mut range_to_replace = {
25081 let replace_range = &completion.replace_range;
25082 if let CompletionSource::Lsp {
25083 insert_range: Some(insert_range),
25084 ..
25085 } = &completion.source
25086 {
25087 debug_assert_eq!(
25088 insert_range.start, replace_range.start,
25089 "insert_range and replace_range should start at the same position"
25090 );
25091 debug_assert!(
25092 insert_range
25093 .start
25094 .cmp(cursor_position, &buffer_snapshot)
25095 .is_le(),
25096 "insert_range should start before or at cursor position"
25097 );
25098 debug_assert!(
25099 replace_range
25100 .start
25101 .cmp(cursor_position, &buffer_snapshot)
25102 .is_le(),
25103 "replace_range should start before or at cursor position"
25104 );
25105
25106 let should_replace = match intent {
25107 CompletionIntent::CompleteWithInsert => false,
25108 CompletionIntent::CompleteWithReplace => true,
25109 CompletionIntent::Complete | CompletionIntent::Compose => {
25110 let insert_mode =
25111 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25112 .completions
25113 .lsp_insert_mode;
25114 match insert_mode {
25115 LspInsertMode::Insert => false,
25116 LspInsertMode::Replace => true,
25117 LspInsertMode::ReplaceSubsequence => {
25118 let mut text_to_replace = buffer.chars_for_range(
25119 buffer.anchor_before(replace_range.start)
25120 ..buffer.anchor_after(replace_range.end),
25121 );
25122 let mut current_needle = text_to_replace.next();
25123 for haystack_ch in completion.label.text.chars() {
25124 if let Some(needle_ch) = current_needle
25125 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25126 {
25127 current_needle = text_to_replace.next();
25128 }
25129 }
25130 current_needle.is_none()
25131 }
25132 LspInsertMode::ReplaceSuffix => {
25133 if replace_range
25134 .end
25135 .cmp(cursor_position, &buffer_snapshot)
25136 .is_gt()
25137 {
25138 let range_after_cursor = *cursor_position..replace_range.end;
25139 let text_after_cursor = buffer
25140 .text_for_range(
25141 buffer.anchor_before(range_after_cursor.start)
25142 ..buffer.anchor_after(range_after_cursor.end),
25143 )
25144 .collect::<String>()
25145 .to_ascii_lowercase();
25146 completion
25147 .label
25148 .text
25149 .to_ascii_lowercase()
25150 .ends_with(&text_after_cursor)
25151 } else {
25152 true
25153 }
25154 }
25155 }
25156 }
25157 };
25158
25159 if should_replace {
25160 replace_range.clone()
25161 } else {
25162 insert_range.clone()
25163 }
25164 } else {
25165 replace_range.clone()
25166 }
25167 };
25168
25169 if range_to_replace
25170 .end
25171 .cmp(cursor_position, &buffer_snapshot)
25172 .is_lt()
25173 {
25174 range_to_replace.end = *cursor_position;
25175 }
25176
25177 let replace_range = range_to_replace.to_offset(buffer);
25178 CompletionEdit {
25179 new_text,
25180 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25181 snippet,
25182 }
25183}
25184
25185struct CompletionEdit {
25186 new_text: String,
25187 replace_range: Range<BufferOffset>,
25188 snippet: Option<Snippet>,
25189}
25190
25191fn comment_delimiter_for_newline(
25192 start_point: &Point,
25193 buffer: &MultiBufferSnapshot,
25194 language: &LanguageScope,
25195) -> Option<Arc<str>> {
25196 let delimiters = language.line_comment_prefixes();
25197 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25198 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25199
25200 let num_of_whitespaces = snapshot
25201 .chars_for_range(range.clone())
25202 .take_while(|c| c.is_whitespace())
25203 .count();
25204 let comment_candidate = snapshot
25205 .chars_for_range(range.clone())
25206 .skip(num_of_whitespaces)
25207 .take(max_len_of_delimiter)
25208 .collect::<String>();
25209 let (delimiter, trimmed_len) = delimiters
25210 .iter()
25211 .filter_map(|delimiter| {
25212 let prefix = delimiter.trim_end();
25213 if comment_candidate.starts_with(prefix) {
25214 Some((delimiter, prefix.len()))
25215 } else {
25216 None
25217 }
25218 })
25219 .max_by_key(|(_, len)| *len)?;
25220
25221 if let Some(BlockCommentConfig {
25222 start: block_start, ..
25223 }) = language.block_comment()
25224 {
25225 let block_start_trimmed = block_start.trim_end();
25226 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25227 let line_content = snapshot
25228 .chars_for_range(range)
25229 .skip(num_of_whitespaces)
25230 .take(block_start_trimmed.len())
25231 .collect::<String>();
25232
25233 if line_content.starts_with(block_start_trimmed) {
25234 return None;
25235 }
25236 }
25237 }
25238
25239 let cursor_is_placed_after_comment_marker =
25240 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25241 if cursor_is_placed_after_comment_marker {
25242 Some(delimiter.clone())
25243 } else {
25244 None
25245 }
25246}
25247
25248fn documentation_delimiter_for_newline(
25249 start_point: &Point,
25250 buffer: &MultiBufferSnapshot,
25251 language: &LanguageScope,
25252 newline_config: &mut NewlineConfig,
25253) -> Option<Arc<str>> {
25254 let BlockCommentConfig {
25255 start: start_tag,
25256 end: end_tag,
25257 prefix: delimiter,
25258 tab_size: len,
25259 } = language.documentation_comment()?;
25260 let is_within_block_comment = buffer
25261 .language_scope_at(*start_point)
25262 .is_some_and(|scope| scope.override_name() == Some("comment"));
25263 if !is_within_block_comment {
25264 return None;
25265 }
25266
25267 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25268
25269 let num_of_whitespaces = snapshot
25270 .chars_for_range(range.clone())
25271 .take_while(|c| c.is_whitespace())
25272 .count();
25273
25274 // 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.
25275 let column = start_point.column;
25276 let cursor_is_after_start_tag = {
25277 let start_tag_len = start_tag.len();
25278 let start_tag_line = snapshot
25279 .chars_for_range(range.clone())
25280 .skip(num_of_whitespaces)
25281 .take(start_tag_len)
25282 .collect::<String>();
25283 if start_tag_line.starts_with(start_tag.as_ref()) {
25284 num_of_whitespaces + start_tag_len <= column as usize
25285 } else {
25286 false
25287 }
25288 };
25289
25290 let cursor_is_after_delimiter = {
25291 let delimiter_trim = delimiter.trim_end();
25292 let delimiter_line = snapshot
25293 .chars_for_range(range.clone())
25294 .skip(num_of_whitespaces)
25295 .take(delimiter_trim.len())
25296 .collect::<String>();
25297 if delimiter_line.starts_with(delimiter_trim) {
25298 num_of_whitespaces + delimiter_trim.len() <= column as usize
25299 } else {
25300 false
25301 }
25302 };
25303
25304 let mut needs_extra_line = false;
25305 let mut extra_line_additional_indent = IndentSize::spaces(0);
25306
25307 let cursor_is_before_end_tag_if_exists = {
25308 let mut char_position = 0u32;
25309 let mut end_tag_offset = None;
25310
25311 'outer: for chunk in snapshot.text_for_range(range) {
25312 if let Some(byte_pos) = chunk.find(&**end_tag) {
25313 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25314 end_tag_offset = Some(char_position + chars_before_match);
25315 break 'outer;
25316 }
25317 char_position += chunk.chars().count() as u32;
25318 }
25319
25320 if let Some(end_tag_offset) = end_tag_offset {
25321 let cursor_is_before_end_tag = column <= end_tag_offset;
25322 if cursor_is_after_start_tag {
25323 if cursor_is_before_end_tag {
25324 needs_extra_line = true;
25325 }
25326 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25327 if cursor_is_at_start_of_end_tag {
25328 extra_line_additional_indent.len = *len;
25329 }
25330 }
25331 cursor_is_before_end_tag
25332 } else {
25333 true
25334 }
25335 };
25336
25337 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25338 && cursor_is_before_end_tag_if_exists
25339 {
25340 let additional_indent = if cursor_is_after_start_tag {
25341 IndentSize::spaces(*len)
25342 } else {
25343 IndentSize::spaces(0)
25344 };
25345
25346 *newline_config = NewlineConfig::Newline {
25347 additional_indent,
25348 extra_line_additional_indent: if needs_extra_line {
25349 Some(extra_line_additional_indent)
25350 } else {
25351 None
25352 },
25353 prevent_auto_indent: true,
25354 };
25355 Some(delimiter.clone())
25356 } else {
25357 None
25358 }
25359}
25360
25361const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25362
25363fn list_delimiter_for_newline(
25364 start_point: &Point,
25365 buffer: &MultiBufferSnapshot,
25366 language: &LanguageScope,
25367 newline_config: &mut NewlineConfig,
25368) -> Option<Arc<str>> {
25369 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25370
25371 let num_of_whitespaces = snapshot
25372 .chars_for_range(range.clone())
25373 .take_while(|c| c.is_whitespace())
25374 .count();
25375
25376 let task_list_entries: Vec<_> = language
25377 .task_list()
25378 .into_iter()
25379 .flat_map(|config| {
25380 config
25381 .prefixes
25382 .iter()
25383 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25384 })
25385 .collect();
25386 let unordered_list_entries: Vec<_> = language
25387 .unordered_list()
25388 .iter()
25389 .map(|marker| (marker.as_ref(), marker.as_ref()))
25390 .collect();
25391
25392 let all_entries: Vec<_> = task_list_entries
25393 .into_iter()
25394 .chain(unordered_list_entries)
25395 .collect();
25396
25397 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25398 let candidate: String = snapshot
25399 .chars_for_range(range.clone())
25400 .skip(num_of_whitespaces)
25401 .take(max_prefix_len)
25402 .collect();
25403
25404 if let Some((prefix, continuation)) = all_entries
25405 .iter()
25406 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25407 .max_by_key(|(prefix, _)| prefix.len())
25408 {
25409 let end_of_prefix = num_of_whitespaces + prefix.len();
25410 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25411 let has_content_after_marker = snapshot
25412 .chars_for_range(range)
25413 .skip(end_of_prefix)
25414 .any(|c| !c.is_whitespace());
25415
25416 if has_content_after_marker && cursor_is_after_prefix {
25417 return Some((*continuation).into());
25418 }
25419
25420 if start_point.column as usize == end_of_prefix {
25421 if num_of_whitespaces == 0 {
25422 *newline_config = NewlineConfig::ClearCurrentLine;
25423 } else {
25424 *newline_config = NewlineConfig::UnindentCurrentLine {
25425 continuation: (*continuation).into(),
25426 };
25427 }
25428 }
25429
25430 return None;
25431 }
25432 }
25433
25434 let candidate: String = snapshot
25435 .chars_for_range(range.clone())
25436 .skip(num_of_whitespaces)
25437 .take(ORDERED_LIST_MAX_MARKER_LEN)
25438 .collect();
25439
25440 for ordered_config in language.ordered_list() {
25441 let regex = match Regex::new(&ordered_config.pattern) {
25442 Ok(r) => r,
25443 Err(_) => continue,
25444 };
25445
25446 if let Some(captures) = regex.captures(&candidate) {
25447 let full_match = captures.get(0)?;
25448 let marker_len = full_match.len();
25449 let end_of_prefix = num_of_whitespaces + marker_len;
25450 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25451
25452 let has_content_after_marker = snapshot
25453 .chars_for_range(range)
25454 .skip(end_of_prefix)
25455 .any(|c| !c.is_whitespace());
25456
25457 if has_content_after_marker && cursor_is_after_prefix {
25458 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25459 let continuation = ordered_config
25460 .format
25461 .replace("{1}", &(number + 1).to_string());
25462 return Some(continuation.into());
25463 }
25464
25465 if start_point.column as usize == end_of_prefix {
25466 let continuation = ordered_config.format.replace("{1}", "1");
25467 if num_of_whitespaces == 0 {
25468 *newline_config = NewlineConfig::ClearCurrentLine;
25469 } else {
25470 *newline_config = NewlineConfig::UnindentCurrentLine {
25471 continuation: continuation.into(),
25472 };
25473 }
25474 }
25475
25476 return None;
25477 }
25478 }
25479
25480 None
25481}
25482
25483fn is_list_prefix_row(
25484 row: MultiBufferRow,
25485 buffer: &MultiBufferSnapshot,
25486 language: &LanguageScope,
25487) -> bool {
25488 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25489 return false;
25490 };
25491
25492 let num_of_whitespaces = snapshot
25493 .chars_for_range(range.clone())
25494 .take_while(|c| c.is_whitespace())
25495 .count();
25496
25497 let task_list_prefixes: Vec<_> = language
25498 .task_list()
25499 .into_iter()
25500 .flat_map(|config| {
25501 config
25502 .prefixes
25503 .iter()
25504 .map(|p| p.as_ref())
25505 .collect::<Vec<_>>()
25506 })
25507 .collect();
25508 let unordered_list_markers: Vec<_> = language
25509 .unordered_list()
25510 .iter()
25511 .map(|marker| marker.as_ref())
25512 .collect();
25513 let all_prefixes: Vec<_> = task_list_prefixes
25514 .into_iter()
25515 .chain(unordered_list_markers)
25516 .collect();
25517 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25518 let candidate: String = snapshot
25519 .chars_for_range(range.clone())
25520 .skip(num_of_whitespaces)
25521 .take(max_prefix_len)
25522 .collect();
25523 if all_prefixes
25524 .iter()
25525 .any(|prefix| candidate.starts_with(*prefix))
25526 {
25527 return true;
25528 }
25529 }
25530
25531 let ordered_list_candidate: String = snapshot
25532 .chars_for_range(range)
25533 .skip(num_of_whitespaces)
25534 .take(ORDERED_LIST_MAX_MARKER_LEN)
25535 .collect();
25536 for ordered_config in language.ordered_list() {
25537 let regex = match Regex::new(&ordered_config.pattern) {
25538 Ok(r) => r,
25539 Err(_) => continue,
25540 };
25541 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25542 return captures.get(0).is_some();
25543 }
25544 }
25545
25546 false
25547}
25548
25549#[derive(Debug)]
25550enum NewlineConfig {
25551 /// Insert newline with optional additional indent and optional extra blank line
25552 Newline {
25553 additional_indent: IndentSize,
25554 extra_line_additional_indent: Option<IndentSize>,
25555 prevent_auto_indent: bool,
25556 },
25557 /// Clear the current line
25558 ClearCurrentLine,
25559 /// Unindent the current line and add continuation
25560 UnindentCurrentLine { continuation: Arc<str> },
25561}
25562
25563impl NewlineConfig {
25564 fn has_extra_line(&self) -> bool {
25565 matches!(
25566 self,
25567 Self::Newline {
25568 extra_line_additional_indent: Some(_),
25569 ..
25570 }
25571 )
25572 }
25573
25574 fn insert_extra_newline_brackets(
25575 buffer: &MultiBufferSnapshot,
25576 range: Range<MultiBufferOffset>,
25577 language: &language::LanguageScope,
25578 ) -> bool {
25579 let leading_whitespace_len = buffer
25580 .reversed_chars_at(range.start)
25581 .take_while(|c| c.is_whitespace() && *c != '\n')
25582 .map(|c| c.len_utf8())
25583 .sum::<usize>();
25584 let trailing_whitespace_len = buffer
25585 .chars_at(range.end)
25586 .take_while(|c| c.is_whitespace() && *c != '\n')
25587 .map(|c| c.len_utf8())
25588 .sum::<usize>();
25589 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25590
25591 language.brackets().any(|(pair, enabled)| {
25592 let pair_start = pair.start.trim_end();
25593 let pair_end = pair.end.trim_start();
25594
25595 enabled
25596 && pair.newline
25597 && buffer.contains_str_at(range.end, pair_end)
25598 && buffer.contains_str_at(
25599 range.start.saturating_sub_usize(pair_start.len()),
25600 pair_start,
25601 )
25602 })
25603 }
25604
25605 fn insert_extra_newline_tree_sitter(
25606 buffer: &MultiBufferSnapshot,
25607 range: Range<MultiBufferOffset>,
25608 ) -> bool {
25609 let (buffer, range) = match buffer
25610 .range_to_buffer_ranges(range.start..=range.end)
25611 .as_slice()
25612 {
25613 [(buffer, range, _)] => (*buffer, range.clone()),
25614 _ => return false,
25615 };
25616 let pair = {
25617 let mut result: Option<BracketMatch<usize>> = None;
25618
25619 for pair in buffer
25620 .all_bracket_ranges(range.start.0..range.end.0)
25621 .filter(move |pair| {
25622 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
25623 })
25624 {
25625 let len = pair.close_range.end - pair.open_range.start;
25626
25627 if let Some(existing) = &result {
25628 let existing_len = existing.close_range.end - existing.open_range.start;
25629 if len > existing_len {
25630 continue;
25631 }
25632 }
25633
25634 result = Some(pair);
25635 }
25636
25637 result
25638 };
25639 let Some(pair) = pair else {
25640 return false;
25641 };
25642 pair.newline_only
25643 && buffer
25644 .chars_for_range(pair.open_range.end..range.start.0)
25645 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
25646 .all(|c| c.is_whitespace() && c != '\n')
25647 }
25648}
25649
25650fn update_uncommitted_diff_for_buffer(
25651 editor: Entity<Editor>,
25652 project: &Entity<Project>,
25653 buffers: impl IntoIterator<Item = Entity<Buffer>>,
25654 buffer: Entity<MultiBuffer>,
25655 cx: &mut App,
25656) -> Task<()> {
25657 let mut tasks = Vec::new();
25658 project.update(cx, |project, cx| {
25659 for buffer in buffers {
25660 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
25661 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
25662 }
25663 }
25664 });
25665 cx.spawn(async move |cx| {
25666 let diffs = future::join_all(tasks).await;
25667 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
25668 return;
25669 }
25670
25671 buffer.update(cx, |buffer, cx| {
25672 for diff in diffs.into_iter().flatten() {
25673 buffer.add_diff(diff, cx);
25674 }
25675 });
25676 })
25677}
25678
25679fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
25680 let tab_size = tab_size.get() as usize;
25681 let mut width = offset;
25682
25683 for ch in text.chars() {
25684 width += if ch == '\t' {
25685 tab_size - (width % tab_size)
25686 } else {
25687 1
25688 };
25689 }
25690
25691 width - offset
25692}
25693
25694#[cfg(test)]
25695mod tests {
25696 use super::*;
25697
25698 #[test]
25699 fn test_string_size_with_expanded_tabs() {
25700 let nz = |val| NonZeroU32::new(val).unwrap();
25701 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
25702 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
25703 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
25704 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
25705 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
25706 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
25707 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
25708 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
25709 }
25710}
25711
25712/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
25713struct WordBreakingTokenizer<'a> {
25714 input: &'a str,
25715}
25716
25717impl<'a> WordBreakingTokenizer<'a> {
25718 fn new(input: &'a str) -> Self {
25719 Self { input }
25720 }
25721}
25722
25723fn is_char_ideographic(ch: char) -> bool {
25724 use unicode_script::Script::*;
25725 use unicode_script::UnicodeScript;
25726 matches!(ch.script(), Han | Tangut | Yi)
25727}
25728
25729fn is_grapheme_ideographic(text: &str) -> bool {
25730 text.chars().any(is_char_ideographic)
25731}
25732
25733fn is_grapheme_whitespace(text: &str) -> bool {
25734 text.chars().any(|x| x.is_whitespace())
25735}
25736
25737fn should_stay_with_preceding_ideograph(text: &str) -> bool {
25738 text.chars()
25739 .next()
25740 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
25741}
25742
25743#[derive(PartialEq, Eq, Debug, Clone, Copy)]
25744enum WordBreakToken<'a> {
25745 Word { token: &'a str, grapheme_len: usize },
25746 InlineWhitespace { token: &'a str, grapheme_len: usize },
25747 Newline,
25748}
25749
25750impl<'a> Iterator for WordBreakingTokenizer<'a> {
25751 /// Yields a span, the count of graphemes in the token, and whether it was
25752 /// whitespace. Note that it also breaks at word boundaries.
25753 type Item = WordBreakToken<'a>;
25754
25755 fn next(&mut self) -> Option<Self::Item> {
25756 use unicode_segmentation::UnicodeSegmentation;
25757 if self.input.is_empty() {
25758 return None;
25759 }
25760
25761 let mut iter = self.input.graphemes(true).peekable();
25762 let mut offset = 0;
25763 let mut grapheme_len = 0;
25764 if let Some(first_grapheme) = iter.next() {
25765 let is_newline = first_grapheme == "\n";
25766 let is_whitespace = is_grapheme_whitespace(first_grapheme);
25767 offset += first_grapheme.len();
25768 grapheme_len += 1;
25769 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
25770 if let Some(grapheme) = iter.peek().copied()
25771 && should_stay_with_preceding_ideograph(grapheme)
25772 {
25773 offset += grapheme.len();
25774 grapheme_len += 1;
25775 }
25776 } else {
25777 let mut words = self.input[offset..].split_word_bound_indices().peekable();
25778 let mut next_word_bound = words.peek().copied();
25779 if next_word_bound.is_some_and(|(i, _)| i == 0) {
25780 next_word_bound = words.next();
25781 }
25782 while let Some(grapheme) = iter.peek().copied() {
25783 if next_word_bound.is_some_and(|(i, _)| i == offset) {
25784 break;
25785 };
25786 if is_grapheme_whitespace(grapheme) != is_whitespace
25787 || (grapheme == "\n") != is_newline
25788 {
25789 break;
25790 };
25791 offset += grapheme.len();
25792 grapheme_len += 1;
25793 iter.next();
25794 }
25795 }
25796 let token = &self.input[..offset];
25797 self.input = &self.input[offset..];
25798 if token == "\n" {
25799 Some(WordBreakToken::Newline)
25800 } else if is_whitespace {
25801 Some(WordBreakToken::InlineWhitespace {
25802 token,
25803 grapheme_len,
25804 })
25805 } else {
25806 Some(WordBreakToken::Word {
25807 token,
25808 grapheme_len,
25809 })
25810 }
25811 } else {
25812 None
25813 }
25814 }
25815}
25816
25817#[test]
25818fn test_word_breaking_tokenizer() {
25819 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
25820 ("", &[]),
25821 (" ", &[whitespace(" ", 2)]),
25822 ("Ʒ", &[word("Ʒ", 1)]),
25823 ("Ǽ", &[word("Ǽ", 1)]),
25824 ("⋑", &[word("⋑", 1)]),
25825 ("⋑⋑", &[word("⋑⋑", 2)]),
25826 (
25827 "原理,进而",
25828 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
25829 ),
25830 (
25831 "hello world",
25832 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
25833 ),
25834 (
25835 "hello, world",
25836 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
25837 ),
25838 (
25839 " hello world",
25840 &[
25841 whitespace(" ", 2),
25842 word("hello", 5),
25843 whitespace(" ", 1),
25844 word("world", 5),
25845 ],
25846 ),
25847 (
25848 "这是什么 \n 钢笔",
25849 &[
25850 word("这", 1),
25851 word("是", 1),
25852 word("什", 1),
25853 word("么", 1),
25854 whitespace(" ", 1),
25855 newline(),
25856 whitespace(" ", 1),
25857 word("钢", 1),
25858 word("笔", 1),
25859 ],
25860 ),
25861 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
25862 ];
25863
25864 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
25865 WordBreakToken::Word {
25866 token,
25867 grapheme_len,
25868 }
25869 }
25870
25871 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
25872 WordBreakToken::InlineWhitespace {
25873 token,
25874 grapheme_len,
25875 }
25876 }
25877
25878 fn newline() -> WordBreakToken<'static> {
25879 WordBreakToken::Newline
25880 }
25881
25882 for (input, result) in tests {
25883 assert_eq!(
25884 WordBreakingTokenizer::new(input)
25885 .collect::<Vec<_>>()
25886 .as_slice(),
25887 *result,
25888 );
25889 }
25890}
25891
25892fn wrap_with_prefix(
25893 first_line_prefix: String,
25894 subsequent_lines_prefix: String,
25895 unwrapped_text: String,
25896 wrap_column: usize,
25897 tab_size: NonZeroU32,
25898 preserve_existing_whitespace: bool,
25899) -> String {
25900 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
25901 let subsequent_lines_prefix_len =
25902 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
25903 let mut wrapped_text = String::new();
25904 let mut current_line = first_line_prefix;
25905 let mut is_first_line = true;
25906
25907 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
25908 let mut current_line_len = first_line_prefix_len;
25909 let mut in_whitespace = false;
25910 for token in tokenizer {
25911 let have_preceding_whitespace = in_whitespace;
25912 match token {
25913 WordBreakToken::Word {
25914 token,
25915 grapheme_len,
25916 } => {
25917 in_whitespace = false;
25918 let current_prefix_len = if is_first_line {
25919 first_line_prefix_len
25920 } else {
25921 subsequent_lines_prefix_len
25922 };
25923 if current_line_len + grapheme_len > wrap_column
25924 && current_line_len != current_prefix_len
25925 {
25926 wrapped_text.push_str(current_line.trim_end());
25927 wrapped_text.push('\n');
25928 is_first_line = false;
25929 current_line = subsequent_lines_prefix.clone();
25930 current_line_len = subsequent_lines_prefix_len;
25931 }
25932 current_line.push_str(token);
25933 current_line_len += grapheme_len;
25934 }
25935 WordBreakToken::InlineWhitespace {
25936 mut token,
25937 mut grapheme_len,
25938 } => {
25939 in_whitespace = true;
25940 if have_preceding_whitespace && !preserve_existing_whitespace {
25941 continue;
25942 }
25943 if !preserve_existing_whitespace {
25944 // Keep a single whitespace grapheme as-is
25945 if let Some(first) =
25946 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
25947 {
25948 token = first;
25949 } else {
25950 token = " ";
25951 }
25952 grapheme_len = 1;
25953 }
25954 let current_prefix_len = if is_first_line {
25955 first_line_prefix_len
25956 } else {
25957 subsequent_lines_prefix_len
25958 };
25959 if current_line_len + grapheme_len > wrap_column {
25960 wrapped_text.push_str(current_line.trim_end());
25961 wrapped_text.push('\n');
25962 is_first_line = false;
25963 current_line = subsequent_lines_prefix.clone();
25964 current_line_len = subsequent_lines_prefix_len;
25965 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
25966 current_line.push_str(token);
25967 current_line_len += grapheme_len;
25968 }
25969 }
25970 WordBreakToken::Newline => {
25971 in_whitespace = true;
25972 let current_prefix_len = if is_first_line {
25973 first_line_prefix_len
25974 } else {
25975 subsequent_lines_prefix_len
25976 };
25977 if preserve_existing_whitespace {
25978 wrapped_text.push_str(current_line.trim_end());
25979 wrapped_text.push('\n');
25980 is_first_line = false;
25981 current_line = subsequent_lines_prefix.clone();
25982 current_line_len = subsequent_lines_prefix_len;
25983 } else if have_preceding_whitespace {
25984 continue;
25985 } else if current_line_len + 1 > wrap_column
25986 && current_line_len != current_prefix_len
25987 {
25988 wrapped_text.push_str(current_line.trim_end());
25989 wrapped_text.push('\n');
25990 is_first_line = false;
25991 current_line = subsequent_lines_prefix.clone();
25992 current_line_len = subsequent_lines_prefix_len;
25993 } else if current_line_len != current_prefix_len {
25994 current_line.push(' ');
25995 current_line_len += 1;
25996 }
25997 }
25998 }
25999 }
26000
26001 if !current_line.is_empty() {
26002 wrapped_text.push_str(¤t_line);
26003 }
26004 wrapped_text
26005}
26006
26007#[test]
26008fn test_wrap_with_prefix() {
26009 assert_eq!(
26010 wrap_with_prefix(
26011 "# ".to_string(),
26012 "# ".to_string(),
26013 "abcdefg".to_string(),
26014 4,
26015 NonZeroU32::new(4).unwrap(),
26016 false,
26017 ),
26018 "# abcdefg"
26019 );
26020 assert_eq!(
26021 wrap_with_prefix(
26022 "".to_string(),
26023 "".to_string(),
26024 "\thello world".to_string(),
26025 8,
26026 NonZeroU32::new(4).unwrap(),
26027 false,
26028 ),
26029 "hello\nworld"
26030 );
26031 assert_eq!(
26032 wrap_with_prefix(
26033 "// ".to_string(),
26034 "// ".to_string(),
26035 "xx \nyy zz aa bb cc".to_string(),
26036 12,
26037 NonZeroU32::new(4).unwrap(),
26038 false,
26039 ),
26040 "// xx yy zz\n// aa bb cc"
26041 );
26042 assert_eq!(
26043 wrap_with_prefix(
26044 String::new(),
26045 String::new(),
26046 "这是什么 \n 钢笔".to_string(),
26047 3,
26048 NonZeroU32::new(4).unwrap(),
26049 false,
26050 ),
26051 "这是什\n么 钢\n笔"
26052 );
26053 assert_eq!(
26054 wrap_with_prefix(
26055 String::new(),
26056 String::new(),
26057 format!("foo{}bar", '\u{2009}'), // thin space
26058 80,
26059 NonZeroU32::new(4).unwrap(),
26060 false,
26061 ),
26062 format!("foo{}bar", '\u{2009}')
26063 );
26064}
26065
26066pub trait CollaborationHub {
26067 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26068 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26069 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26070}
26071
26072impl CollaborationHub for Entity<Project> {
26073 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26074 self.read(cx).collaborators()
26075 }
26076
26077 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26078 self.read(cx).user_store().read(cx).participant_indices()
26079 }
26080
26081 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26082 let this = self.read(cx);
26083 let user_ids = this.collaborators().values().map(|c| c.user_id);
26084 this.user_store().read(cx).participant_names(user_ids, cx)
26085 }
26086}
26087
26088pub trait SemanticsProvider {
26089 fn hover(
26090 &self,
26091 buffer: &Entity<Buffer>,
26092 position: text::Anchor,
26093 cx: &mut App,
26094 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26095
26096 fn inline_values(
26097 &self,
26098 buffer_handle: Entity<Buffer>,
26099 range: Range<text::Anchor>,
26100 cx: &mut App,
26101 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26102
26103 fn applicable_inlay_chunks(
26104 &self,
26105 buffer: &Entity<Buffer>,
26106 ranges: &[Range<text::Anchor>],
26107 cx: &mut App,
26108 ) -> Vec<Range<BufferRow>>;
26109
26110 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26111
26112 fn inlay_hints(
26113 &self,
26114 invalidate: InvalidationStrategy,
26115 buffer: Entity<Buffer>,
26116 ranges: Vec<Range<text::Anchor>>,
26117 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26118 cx: &mut App,
26119 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26120
26121 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26122
26123 fn document_highlights(
26124 &self,
26125 buffer: &Entity<Buffer>,
26126 position: text::Anchor,
26127 cx: &mut App,
26128 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26129
26130 fn definitions(
26131 &self,
26132 buffer: &Entity<Buffer>,
26133 position: text::Anchor,
26134 kind: GotoDefinitionKind,
26135 cx: &mut App,
26136 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26137
26138 fn range_for_rename(
26139 &self,
26140 buffer: &Entity<Buffer>,
26141 position: text::Anchor,
26142 cx: &mut App,
26143 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26144
26145 fn perform_rename(
26146 &self,
26147 buffer: &Entity<Buffer>,
26148 position: text::Anchor,
26149 new_name: String,
26150 cx: &mut App,
26151 ) -> Option<Task<Result<ProjectTransaction>>>;
26152}
26153
26154pub trait CompletionProvider {
26155 fn completions(
26156 &self,
26157 excerpt_id: ExcerptId,
26158 buffer: &Entity<Buffer>,
26159 buffer_position: text::Anchor,
26160 trigger: CompletionContext,
26161 window: &mut Window,
26162 cx: &mut Context<Editor>,
26163 ) -> Task<Result<Vec<CompletionResponse>>>;
26164
26165 fn resolve_completions(
26166 &self,
26167 _buffer: Entity<Buffer>,
26168 _completion_indices: Vec<usize>,
26169 _completions: Rc<RefCell<Box<[Completion]>>>,
26170 _cx: &mut Context<Editor>,
26171 ) -> Task<Result<bool>> {
26172 Task::ready(Ok(false))
26173 }
26174
26175 fn apply_additional_edits_for_completion(
26176 &self,
26177 _buffer: Entity<Buffer>,
26178 _completions: Rc<RefCell<Box<[Completion]>>>,
26179 _completion_index: usize,
26180 _push_to_history: bool,
26181 _cx: &mut Context<Editor>,
26182 ) -> Task<Result<Option<language::Transaction>>> {
26183 Task::ready(Ok(None))
26184 }
26185
26186 fn is_completion_trigger(
26187 &self,
26188 buffer: &Entity<Buffer>,
26189 position: language::Anchor,
26190 text: &str,
26191 trigger_in_words: bool,
26192 cx: &mut Context<Editor>,
26193 ) -> bool;
26194
26195 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26196
26197 fn sort_completions(&self) -> bool {
26198 true
26199 }
26200
26201 fn filter_completions(&self) -> bool {
26202 true
26203 }
26204
26205 fn show_snippets(&self) -> bool {
26206 false
26207 }
26208}
26209
26210pub trait CodeActionProvider {
26211 fn id(&self) -> Arc<str>;
26212
26213 fn code_actions(
26214 &self,
26215 buffer: &Entity<Buffer>,
26216 range: Range<text::Anchor>,
26217 window: &mut Window,
26218 cx: &mut App,
26219 ) -> Task<Result<Vec<CodeAction>>>;
26220
26221 fn apply_code_action(
26222 &self,
26223 buffer_handle: Entity<Buffer>,
26224 action: CodeAction,
26225 excerpt_id: ExcerptId,
26226 push_to_history: bool,
26227 window: &mut Window,
26228 cx: &mut App,
26229 ) -> Task<Result<ProjectTransaction>>;
26230}
26231
26232impl CodeActionProvider for Entity<Project> {
26233 fn id(&self) -> Arc<str> {
26234 "project".into()
26235 }
26236
26237 fn code_actions(
26238 &self,
26239 buffer: &Entity<Buffer>,
26240 range: Range<text::Anchor>,
26241 _window: &mut Window,
26242 cx: &mut App,
26243 ) -> Task<Result<Vec<CodeAction>>> {
26244 self.update(cx, |project, cx| {
26245 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26246 let code_actions = project.code_actions(buffer, range, None, cx);
26247 cx.background_spawn(async move {
26248 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26249 Ok(code_lens_actions
26250 .context("code lens fetch")?
26251 .into_iter()
26252 .flatten()
26253 .chain(
26254 code_actions
26255 .context("code action fetch")?
26256 .into_iter()
26257 .flatten(),
26258 )
26259 .collect())
26260 })
26261 })
26262 }
26263
26264 fn apply_code_action(
26265 &self,
26266 buffer_handle: Entity<Buffer>,
26267 action: CodeAction,
26268 _excerpt_id: ExcerptId,
26269 push_to_history: bool,
26270 _window: &mut Window,
26271 cx: &mut App,
26272 ) -> Task<Result<ProjectTransaction>> {
26273 self.update(cx, |project, cx| {
26274 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26275 })
26276 }
26277}
26278
26279fn snippet_completions(
26280 project: &Project,
26281 buffer: &Entity<Buffer>,
26282 buffer_anchor: text::Anchor,
26283 classifier: CharClassifier,
26284 cx: &mut App,
26285) -> Task<Result<CompletionResponse>> {
26286 let languages = buffer.read(cx).languages_at(buffer_anchor);
26287 let snippet_store = project.snippets().read(cx);
26288
26289 let scopes: Vec<_> = languages
26290 .iter()
26291 .filter_map(|language| {
26292 let language_name = language.lsp_id();
26293 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26294
26295 if snippets.is_empty() {
26296 None
26297 } else {
26298 Some((language.default_scope(), snippets))
26299 }
26300 })
26301 .collect();
26302
26303 if scopes.is_empty() {
26304 return Task::ready(Ok(CompletionResponse {
26305 completions: vec![],
26306 display_options: CompletionDisplayOptions::default(),
26307 is_incomplete: false,
26308 }));
26309 }
26310
26311 let snapshot = buffer.read(cx).text_snapshot();
26312 let executor = cx.background_executor().clone();
26313
26314 cx.background_spawn(async move {
26315 let is_word_char = |c| classifier.is_word(c);
26316
26317 let mut is_incomplete = false;
26318 let mut completions: Vec<Completion> = Vec::new();
26319
26320 const MAX_PREFIX_LEN: usize = 128;
26321 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26322 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26323 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26324
26325 let max_buffer_window: String = snapshot
26326 .text_for_range(window_start..buffer_offset)
26327 .collect();
26328
26329 if max_buffer_window.is_empty() {
26330 return Ok(CompletionResponse {
26331 completions: vec![],
26332 display_options: CompletionDisplayOptions::default(),
26333 is_incomplete: true,
26334 });
26335 }
26336
26337 for (_scope, snippets) in scopes.into_iter() {
26338 // Sort snippets by word count to match longer snippet prefixes first.
26339 let mut sorted_snippet_candidates = snippets
26340 .iter()
26341 .enumerate()
26342 .flat_map(|(snippet_ix, snippet)| {
26343 snippet
26344 .prefix
26345 .iter()
26346 .enumerate()
26347 .map(move |(prefix_ix, prefix)| {
26348 let word_count =
26349 snippet_candidate_suffixes(prefix, is_word_char).count();
26350 ((snippet_ix, prefix_ix), prefix, word_count)
26351 })
26352 })
26353 .collect_vec();
26354 sorted_snippet_candidates
26355 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26356
26357 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26358
26359 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26360 .take(
26361 sorted_snippet_candidates
26362 .first()
26363 .map(|(_, _, word_count)| *word_count)
26364 .unwrap_or_default(),
26365 )
26366 .collect_vec();
26367
26368 const MAX_RESULTS: usize = 100;
26369 // Each match also remembers how many characters from the buffer it consumed
26370 let mut matches: Vec<(StringMatch, usize)> = vec![];
26371
26372 let mut snippet_list_cutoff_index = 0;
26373 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26374 let word_count = buffer_index + 1;
26375 // Increase `snippet_list_cutoff_index` until we have all of the
26376 // snippets with sufficiently many words.
26377 while sorted_snippet_candidates
26378 .get(snippet_list_cutoff_index)
26379 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26380 *snippet_word_count >= word_count
26381 })
26382 {
26383 snippet_list_cutoff_index += 1;
26384 }
26385
26386 // Take only the candidates with at least `word_count` many words
26387 let snippet_candidates_at_word_len =
26388 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26389
26390 let candidates = snippet_candidates_at_word_len
26391 .iter()
26392 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26393 .enumerate() // index in `sorted_snippet_candidates`
26394 // First char must match
26395 .filter(|(_ix, prefix)| {
26396 itertools::equal(
26397 prefix
26398 .chars()
26399 .next()
26400 .into_iter()
26401 .flat_map(|c| c.to_lowercase()),
26402 buffer_window
26403 .chars()
26404 .next()
26405 .into_iter()
26406 .flat_map(|c| c.to_lowercase()),
26407 )
26408 })
26409 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26410 .collect::<Vec<StringMatchCandidate>>();
26411
26412 matches.extend(
26413 fuzzy::match_strings(
26414 &candidates,
26415 &buffer_window,
26416 buffer_window.chars().any(|c| c.is_uppercase()),
26417 true,
26418 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26419 &Default::default(),
26420 executor.clone(),
26421 )
26422 .await
26423 .into_iter()
26424 .map(|string_match| (string_match, buffer_window.len())),
26425 );
26426
26427 if matches.len() >= MAX_RESULTS {
26428 break;
26429 }
26430 }
26431
26432 let to_lsp = |point: &text::Anchor| {
26433 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26434 point_to_lsp(end)
26435 };
26436 let lsp_end = to_lsp(&buffer_anchor);
26437
26438 if matches.len() >= MAX_RESULTS {
26439 is_incomplete = true;
26440 }
26441
26442 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26443 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26444 sorted_snippet_candidates[string_match.candidate_id];
26445 let snippet = &snippets[snippet_index];
26446 let start = buffer_offset - buffer_window_len;
26447 let start = snapshot.anchor_before(start);
26448 let range = start..buffer_anchor;
26449 let lsp_start = to_lsp(&start);
26450 let lsp_range = lsp::Range {
26451 start: lsp_start,
26452 end: lsp_end,
26453 };
26454 Completion {
26455 replace_range: range,
26456 new_text: snippet.body.clone(),
26457 source: CompletionSource::Lsp {
26458 insert_range: None,
26459 server_id: LanguageServerId(usize::MAX),
26460 resolved: true,
26461 lsp_completion: Box::new(lsp::CompletionItem {
26462 label: snippet.prefix.first().unwrap().clone(),
26463 kind: Some(CompletionItemKind::SNIPPET),
26464 label_details: snippet.description.as_ref().map(|description| {
26465 lsp::CompletionItemLabelDetails {
26466 detail: Some(description.clone()),
26467 description: None,
26468 }
26469 }),
26470 insert_text_format: Some(InsertTextFormat::SNIPPET),
26471 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26472 lsp::InsertReplaceEdit {
26473 new_text: snippet.body.clone(),
26474 insert: lsp_range,
26475 replace: lsp_range,
26476 },
26477 )),
26478 filter_text: Some(snippet.body.clone()),
26479 sort_text: Some(char::MAX.to_string()),
26480 ..lsp::CompletionItem::default()
26481 }),
26482 lsp_defaults: None,
26483 },
26484 label: CodeLabel {
26485 text: matching_prefix.clone(),
26486 runs: Vec::new(),
26487 filter_range: 0..matching_prefix.len(),
26488 },
26489 icon_path: None,
26490 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26491 single_line: snippet.name.clone().into(),
26492 plain_text: snippet
26493 .description
26494 .clone()
26495 .map(|description| description.into()),
26496 }),
26497 insert_text_mode: None,
26498 confirm: None,
26499 match_start: Some(start),
26500 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26501 }
26502 }));
26503 }
26504
26505 Ok(CompletionResponse {
26506 completions,
26507 display_options: CompletionDisplayOptions::default(),
26508 is_incomplete,
26509 })
26510 })
26511}
26512
26513impl CompletionProvider for Entity<Project> {
26514 fn completions(
26515 &self,
26516 _excerpt_id: ExcerptId,
26517 buffer: &Entity<Buffer>,
26518 buffer_position: text::Anchor,
26519 options: CompletionContext,
26520 _window: &mut Window,
26521 cx: &mut Context<Editor>,
26522 ) -> Task<Result<Vec<CompletionResponse>>> {
26523 self.update(cx, |project, cx| {
26524 let task = project.completions(buffer, buffer_position, options, cx);
26525 cx.background_spawn(task)
26526 })
26527 }
26528
26529 fn resolve_completions(
26530 &self,
26531 buffer: Entity<Buffer>,
26532 completion_indices: Vec<usize>,
26533 completions: Rc<RefCell<Box<[Completion]>>>,
26534 cx: &mut Context<Editor>,
26535 ) -> Task<Result<bool>> {
26536 self.update(cx, |project, cx| {
26537 project.lsp_store().update(cx, |lsp_store, cx| {
26538 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26539 })
26540 })
26541 }
26542
26543 fn apply_additional_edits_for_completion(
26544 &self,
26545 buffer: Entity<Buffer>,
26546 completions: Rc<RefCell<Box<[Completion]>>>,
26547 completion_index: usize,
26548 push_to_history: bool,
26549 cx: &mut Context<Editor>,
26550 ) -> Task<Result<Option<language::Transaction>>> {
26551 self.update(cx, |project, cx| {
26552 project.lsp_store().update(cx, |lsp_store, cx| {
26553 lsp_store.apply_additional_edits_for_completion(
26554 buffer,
26555 completions,
26556 completion_index,
26557 push_to_history,
26558 cx,
26559 )
26560 })
26561 })
26562 }
26563
26564 fn is_completion_trigger(
26565 &self,
26566 buffer: &Entity<Buffer>,
26567 position: language::Anchor,
26568 text: &str,
26569 trigger_in_words: bool,
26570 cx: &mut Context<Editor>,
26571 ) -> bool {
26572 let mut chars = text.chars();
26573 let char = if let Some(char) = chars.next() {
26574 char
26575 } else {
26576 return false;
26577 };
26578 if chars.next().is_some() {
26579 return false;
26580 }
26581
26582 let buffer = buffer.read(cx);
26583 let snapshot = buffer.snapshot();
26584 let classifier = snapshot
26585 .char_classifier_at(position)
26586 .scope_context(Some(CharScopeContext::Completion));
26587 if trigger_in_words && classifier.is_word(char) {
26588 return true;
26589 }
26590
26591 buffer.completion_triggers().contains(text)
26592 }
26593
26594 fn show_snippets(&self) -> bool {
26595 true
26596 }
26597}
26598
26599impl SemanticsProvider for Entity<Project> {
26600 fn hover(
26601 &self,
26602 buffer: &Entity<Buffer>,
26603 position: text::Anchor,
26604 cx: &mut App,
26605 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26606 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
26607 }
26608
26609 fn document_highlights(
26610 &self,
26611 buffer: &Entity<Buffer>,
26612 position: text::Anchor,
26613 cx: &mut App,
26614 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
26615 Some(self.update(cx, |project, cx| {
26616 project.document_highlights(buffer, position, cx)
26617 }))
26618 }
26619
26620 fn definitions(
26621 &self,
26622 buffer: &Entity<Buffer>,
26623 position: text::Anchor,
26624 kind: GotoDefinitionKind,
26625 cx: &mut App,
26626 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
26627 Some(self.update(cx, |project, cx| match kind {
26628 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
26629 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
26630 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
26631 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
26632 }))
26633 }
26634
26635 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
26636 self.update(cx, |project, cx| {
26637 if project
26638 .active_debug_session(cx)
26639 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
26640 {
26641 return true;
26642 }
26643
26644 buffer.update(cx, |buffer, cx| {
26645 project.any_language_server_supports_inlay_hints(buffer, cx)
26646 })
26647 })
26648 }
26649
26650 fn inline_values(
26651 &self,
26652 buffer_handle: Entity<Buffer>,
26653 range: Range<text::Anchor>,
26654 cx: &mut App,
26655 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
26656 self.update(cx, |project, cx| {
26657 let (session, active_stack_frame) = project.active_debug_session(cx)?;
26658
26659 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
26660 })
26661 }
26662
26663 fn applicable_inlay_chunks(
26664 &self,
26665 buffer: &Entity<Buffer>,
26666 ranges: &[Range<text::Anchor>],
26667 cx: &mut App,
26668 ) -> Vec<Range<BufferRow>> {
26669 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26670 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
26671 })
26672 }
26673
26674 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
26675 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
26676 lsp_store.invalidate_inlay_hints(for_buffers)
26677 });
26678 }
26679
26680 fn inlay_hints(
26681 &self,
26682 invalidate: InvalidationStrategy,
26683 buffer: Entity<Buffer>,
26684 ranges: Vec<Range<text::Anchor>>,
26685 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26686 cx: &mut App,
26687 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
26688 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26689 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
26690 }))
26691 }
26692
26693 fn range_for_rename(
26694 &self,
26695 buffer: &Entity<Buffer>,
26696 position: text::Anchor,
26697 cx: &mut App,
26698 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
26699 Some(self.update(cx, |project, cx| {
26700 let buffer = buffer.clone();
26701 let task = project.prepare_rename(buffer.clone(), position, cx);
26702 cx.spawn(async move |_, cx| {
26703 Ok(match task.await? {
26704 PrepareRenameResponse::Success(range) => Some(range),
26705 PrepareRenameResponse::InvalidPosition => None,
26706 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
26707 // Fallback on using TreeSitter info to determine identifier range
26708 buffer.read_with(cx, |buffer, _| {
26709 let snapshot = buffer.snapshot();
26710 let (range, kind) = snapshot.surrounding_word(position, None);
26711 if kind != Some(CharKind::Word) {
26712 return None;
26713 }
26714 Some(
26715 snapshot.anchor_before(range.start)
26716 ..snapshot.anchor_after(range.end),
26717 )
26718 })
26719 }
26720 })
26721 })
26722 }))
26723 }
26724
26725 fn perform_rename(
26726 &self,
26727 buffer: &Entity<Buffer>,
26728 position: text::Anchor,
26729 new_name: String,
26730 cx: &mut App,
26731 ) -> Option<Task<Result<ProjectTransaction>>> {
26732 Some(self.update(cx, |project, cx| {
26733 project.perform_rename(buffer.clone(), position, new_name, cx)
26734 }))
26735 }
26736}
26737
26738fn consume_contiguous_rows(
26739 contiguous_row_selections: &mut Vec<Selection<Point>>,
26740 selection: &Selection<Point>,
26741 display_map: &DisplaySnapshot,
26742 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
26743) -> (MultiBufferRow, MultiBufferRow) {
26744 contiguous_row_selections.push(selection.clone());
26745 let start_row = starting_row(selection, display_map);
26746 let mut end_row = ending_row(selection, display_map);
26747
26748 while let Some(next_selection) = selections.peek() {
26749 if next_selection.start.row <= end_row.0 {
26750 end_row = ending_row(next_selection, display_map);
26751 contiguous_row_selections.push(selections.next().unwrap().clone());
26752 } else {
26753 break;
26754 }
26755 }
26756 (start_row, end_row)
26757}
26758
26759fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26760 if selection.start.column > 0 {
26761 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
26762 } else {
26763 MultiBufferRow(selection.start.row)
26764 }
26765}
26766
26767fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26768 if next_selection.end.column > 0 || next_selection.is_empty() {
26769 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
26770 } else {
26771 MultiBufferRow(next_selection.end.row)
26772 }
26773}
26774
26775impl EditorSnapshot {
26776 pub fn remote_selections_in_range<'a>(
26777 &'a self,
26778 range: &'a Range<Anchor>,
26779 collaboration_hub: &dyn CollaborationHub,
26780 cx: &'a App,
26781 ) -> impl 'a + Iterator<Item = RemoteSelection> {
26782 let participant_names = collaboration_hub.user_names(cx);
26783 let participant_indices = collaboration_hub.user_participant_indices(cx);
26784 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
26785 let collaborators_by_replica_id = collaborators_by_peer_id
26786 .values()
26787 .map(|collaborator| (collaborator.replica_id, collaborator))
26788 .collect::<HashMap<_, _>>();
26789 self.buffer_snapshot()
26790 .selections_in_range(range, false)
26791 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
26792 if replica_id == ReplicaId::AGENT {
26793 Some(RemoteSelection {
26794 replica_id,
26795 selection,
26796 cursor_shape,
26797 line_mode,
26798 collaborator_id: CollaboratorId::Agent,
26799 user_name: Some("Agent".into()),
26800 color: cx.theme().players().agent(),
26801 })
26802 } else {
26803 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
26804 let participant_index = participant_indices.get(&collaborator.user_id).copied();
26805 let user_name = participant_names.get(&collaborator.user_id).cloned();
26806 Some(RemoteSelection {
26807 replica_id,
26808 selection,
26809 cursor_shape,
26810 line_mode,
26811 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
26812 user_name,
26813 color: if let Some(index) = participant_index {
26814 cx.theme().players().color_for_participant(index.0)
26815 } else {
26816 cx.theme().players().absent()
26817 },
26818 })
26819 }
26820 })
26821 }
26822
26823 pub fn hunks_for_ranges(
26824 &self,
26825 ranges: impl IntoIterator<Item = Range<Point>>,
26826 ) -> Vec<MultiBufferDiffHunk> {
26827 let mut hunks = Vec::new();
26828 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
26829 HashMap::default();
26830 for query_range in ranges {
26831 let query_rows =
26832 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
26833 for hunk in self.buffer_snapshot().diff_hunks_in_range(
26834 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
26835 ) {
26836 // Include deleted hunks that are adjacent to the query range, because
26837 // otherwise they would be missed.
26838 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
26839 if hunk.status().is_deleted() {
26840 intersects_range |= hunk.row_range.start == query_rows.end;
26841 intersects_range |= hunk.row_range.end == query_rows.start;
26842 }
26843 if intersects_range {
26844 if !processed_buffer_rows
26845 .entry(hunk.buffer_id)
26846 .or_default()
26847 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
26848 {
26849 continue;
26850 }
26851 hunks.push(hunk);
26852 }
26853 }
26854 }
26855
26856 hunks
26857 }
26858
26859 fn display_diff_hunks_for_rows<'a>(
26860 &'a self,
26861 display_rows: Range<DisplayRow>,
26862 folded_buffers: &'a HashSet<BufferId>,
26863 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
26864 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
26865 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
26866
26867 self.buffer_snapshot()
26868 .diff_hunks_in_range(buffer_start..buffer_end)
26869 .filter_map(|hunk| {
26870 if folded_buffers.contains(&hunk.buffer_id) {
26871 return None;
26872 }
26873
26874 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
26875 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
26876
26877 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
26878 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
26879
26880 let display_hunk = if hunk_display_start.column() != 0 {
26881 DisplayDiffHunk::Folded {
26882 display_row: hunk_display_start.row(),
26883 }
26884 } else {
26885 let mut end_row = hunk_display_end.row();
26886 if hunk_display_end.column() > 0 {
26887 end_row.0 += 1;
26888 }
26889 let is_created_file = hunk.is_created_file();
26890
26891 DisplayDiffHunk::Unfolded {
26892 status: hunk.status(),
26893 diff_base_byte_range: hunk.diff_base_byte_range.start.0
26894 ..hunk.diff_base_byte_range.end.0,
26895 word_diffs: hunk.word_diffs,
26896 display_row_range: hunk_display_start.row()..end_row,
26897 multi_buffer_range: Anchor::range_in_buffer(
26898 hunk.excerpt_id,
26899 hunk.buffer_range,
26900 ),
26901 is_created_file,
26902 }
26903 };
26904
26905 Some(display_hunk)
26906 })
26907 }
26908
26909 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
26910 self.display_snapshot
26911 .buffer_snapshot()
26912 .language_at(position)
26913 }
26914
26915 pub fn is_focused(&self) -> bool {
26916 self.is_focused
26917 }
26918
26919 pub fn placeholder_text(&self) -> Option<String> {
26920 self.placeholder_display_snapshot
26921 .as_ref()
26922 .map(|display_map| display_map.text())
26923 }
26924
26925 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
26926 self.scroll_anchor.scroll_position(&self.display_snapshot)
26927 }
26928
26929 pub fn gutter_dimensions(
26930 &self,
26931 font_id: FontId,
26932 font_size: Pixels,
26933 style: &EditorStyle,
26934 window: &mut Window,
26935 cx: &App,
26936 ) -> GutterDimensions {
26937 if self.show_gutter
26938 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
26939 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
26940 {
26941 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
26942 matches!(
26943 ProjectSettings::get_global(cx).git.git_gutter,
26944 GitGutterSetting::TrackedFiles
26945 )
26946 });
26947 let gutter_settings = EditorSettings::get_global(cx).gutter;
26948 let show_line_numbers = self
26949 .show_line_numbers
26950 .unwrap_or(gutter_settings.line_numbers);
26951 let line_gutter_width = if show_line_numbers {
26952 // Avoid flicker-like gutter resizes when the line number gains another digit by
26953 // only resizing the gutter on files with > 10**min_line_number_digits lines.
26954 let min_width_for_number_on_gutter =
26955 ch_advance * gutter_settings.min_line_number_digits as f32;
26956 self.max_line_number_width(style, window)
26957 .max(min_width_for_number_on_gutter)
26958 } else {
26959 0.0.into()
26960 };
26961
26962 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
26963 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
26964
26965 let git_blame_entries_width =
26966 self.git_blame_gutter_max_author_length
26967 .map(|max_author_length| {
26968 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
26969 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
26970
26971 /// The number of characters to dedicate to gaps and margins.
26972 const SPACING_WIDTH: usize = 4;
26973
26974 let max_char_count = max_author_length.min(renderer.max_author_length())
26975 + ::git::SHORT_SHA_LENGTH
26976 + MAX_RELATIVE_TIMESTAMP.len()
26977 + SPACING_WIDTH;
26978
26979 ch_advance * max_char_count
26980 });
26981
26982 let is_singleton = self.buffer_snapshot().is_singleton();
26983
26984 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
26985 left_padding += if !is_singleton {
26986 ch_width * 4.0
26987 } else if show_runnables || show_breakpoints {
26988 ch_width * 3.0
26989 } else if show_git_gutter && show_line_numbers {
26990 ch_width * 2.0
26991 } else if show_git_gutter || show_line_numbers {
26992 ch_width
26993 } else {
26994 px(0.)
26995 };
26996
26997 let shows_folds = is_singleton && gutter_settings.folds;
26998
26999 let right_padding = if shows_folds && show_line_numbers {
27000 ch_width * 4.0
27001 } else if shows_folds || (!is_singleton && show_line_numbers) {
27002 ch_width * 3.0
27003 } else if show_line_numbers {
27004 ch_width
27005 } else {
27006 px(0.)
27007 };
27008
27009 GutterDimensions {
27010 left_padding,
27011 right_padding,
27012 width: line_gutter_width + left_padding + right_padding,
27013 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27014 git_blame_entries_width,
27015 }
27016 } else if self.offset_content {
27017 GutterDimensions::default_with_margin(font_id, font_size, cx)
27018 } else {
27019 GutterDimensions::default()
27020 }
27021 }
27022
27023 pub fn render_crease_toggle(
27024 &self,
27025 buffer_row: MultiBufferRow,
27026 row_contains_cursor: bool,
27027 editor: Entity<Editor>,
27028 window: &mut Window,
27029 cx: &mut App,
27030 ) -> Option<AnyElement> {
27031 let folded = self.is_line_folded(buffer_row);
27032 let mut is_foldable = false;
27033
27034 if let Some(crease) = self
27035 .crease_snapshot
27036 .query_row(buffer_row, self.buffer_snapshot())
27037 {
27038 is_foldable = true;
27039 match crease {
27040 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27041 if let Some(render_toggle) = render_toggle {
27042 let toggle_callback =
27043 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27044 if folded {
27045 editor.update(cx, |editor, cx| {
27046 editor.fold_at(buffer_row, window, cx)
27047 });
27048 } else {
27049 editor.update(cx, |editor, cx| {
27050 editor.unfold_at(buffer_row, window, cx)
27051 });
27052 }
27053 });
27054 return Some((render_toggle)(
27055 buffer_row,
27056 folded,
27057 toggle_callback,
27058 window,
27059 cx,
27060 ));
27061 }
27062 }
27063 }
27064 }
27065
27066 is_foldable |= self.starts_indent(buffer_row);
27067
27068 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27069 Some(
27070 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27071 .toggle_state(folded)
27072 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27073 if folded {
27074 this.unfold_at(buffer_row, window, cx);
27075 } else {
27076 this.fold_at(buffer_row, window, cx);
27077 }
27078 }))
27079 .into_any_element(),
27080 )
27081 } else {
27082 None
27083 }
27084 }
27085
27086 pub fn render_crease_trailer(
27087 &self,
27088 buffer_row: MultiBufferRow,
27089 window: &mut Window,
27090 cx: &mut App,
27091 ) -> Option<AnyElement> {
27092 let folded = self.is_line_folded(buffer_row);
27093 if let Crease::Inline { render_trailer, .. } = self
27094 .crease_snapshot
27095 .query_row(buffer_row, self.buffer_snapshot())?
27096 {
27097 let render_trailer = render_trailer.as_ref()?;
27098 Some(render_trailer(buffer_row, folded, window, cx))
27099 } else {
27100 None
27101 }
27102 }
27103
27104 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27105 let digit_count = self.widest_line_number().ilog10() + 1;
27106 column_pixels(style, digit_count as usize, window)
27107 }
27108
27109 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27110 ///
27111 /// This is positive if `base` is before `line`.
27112 fn relative_line_delta(
27113 &self,
27114 current_selection_head: DisplayRow,
27115 first_visible_row: DisplayRow,
27116 consider_wrapped_lines: bool,
27117 ) -> i64 {
27118 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27119 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27120
27121 if consider_wrapped_lines {
27122 let wrap_snapshot = self.wrap_snapshot();
27123 let base_wrap_row = wrap_snapshot
27124 .make_wrap_point(current_selection_head, Bias::Left)
27125 .row();
27126 let wrap_row = wrap_snapshot
27127 .make_wrap_point(first_visible_row, Bias::Left)
27128 .row();
27129
27130 wrap_row.0 as i64 - base_wrap_row.0 as i64
27131 } else {
27132 let fold_snapshot = self.fold_snapshot();
27133 let base_fold_row = fold_snapshot
27134 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27135 .row();
27136 let fold_row = fold_snapshot
27137 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27138 .row();
27139
27140 fold_row as i64 - base_fold_row as i64
27141 }
27142 }
27143
27144 /// Returns the unsigned relative line number to display for each row in `rows`.
27145 ///
27146 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27147 pub fn calculate_relative_line_numbers(
27148 &self,
27149 rows: &Range<DisplayRow>,
27150 current_selection_head: DisplayRow,
27151 count_wrapped_lines: bool,
27152 ) -> HashMap<DisplayRow, u32> {
27153 let initial_offset =
27154 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27155 let current_selection_point = current_selection_head.as_display_point().to_point(self);
27156
27157 self.row_infos(rows.start)
27158 .take(rows.len())
27159 .enumerate()
27160 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27161 .filter(|(_row, row_info)| {
27162 row_info.buffer_row.is_some()
27163 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27164 })
27165 .enumerate()
27166 .filter(|(_, (row, row_info))| {
27167 // We want to check here that
27168 // - the row is not the current selection head to ensure the current
27169 // line has absolute numbering
27170 // - similarly, should the selection head live in a soft-wrapped line
27171 // and we are not counting those, that the parent line keeps its
27172 // absolute number
27173 // - lastly, if we are in a deleted line, it is fine to number this
27174 // relative with 0, as otherwise it would have no line number at all
27175 (*row != current_selection_head
27176 && (count_wrapped_lines
27177 || row_info.buffer_row != Some(current_selection_point.row)))
27178 || row_info
27179 .diff_status
27180 .is_some_and(|status| status.is_deleted())
27181 })
27182 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
27183 .collect()
27184 }
27185}
27186
27187pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27188 let font_size = style.text.font_size.to_pixels(window.rem_size());
27189 let layout = window.text_system().shape_line(
27190 SharedString::from(" ".repeat(column)),
27191 font_size,
27192 &[TextRun {
27193 len: column,
27194 font: style.text.font(),
27195 color: Hsla::default(),
27196 ..Default::default()
27197 }],
27198 None,
27199 );
27200
27201 layout.width
27202}
27203
27204impl Deref for EditorSnapshot {
27205 type Target = DisplaySnapshot;
27206
27207 fn deref(&self) -> &Self::Target {
27208 &self.display_snapshot
27209 }
27210}
27211
27212#[derive(Clone, Debug, PartialEq, Eq)]
27213pub enum EditorEvent {
27214 /// Emitted when the stored review comments change (added, removed, or updated).
27215 ReviewCommentsChanged {
27216 /// The new total count of review comments.
27217 total_count: usize,
27218 },
27219 InputIgnored {
27220 text: Arc<str>,
27221 },
27222 InputHandled {
27223 utf16_range_to_replace: Option<Range<isize>>,
27224 text: Arc<str>,
27225 },
27226 ExcerptsAdded {
27227 buffer: Entity<Buffer>,
27228 predecessor: ExcerptId,
27229 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27230 },
27231 ExcerptsRemoved {
27232 ids: Vec<ExcerptId>,
27233 removed_buffer_ids: Vec<BufferId>,
27234 },
27235 BufferFoldToggled {
27236 ids: Vec<ExcerptId>,
27237 folded: bool,
27238 },
27239 ExcerptsEdited {
27240 ids: Vec<ExcerptId>,
27241 },
27242 ExcerptsExpanded {
27243 ids: Vec<ExcerptId>,
27244 },
27245 ExpandExcerptsRequested {
27246 excerpt_ids: Vec<ExcerptId>,
27247 lines: u32,
27248 direction: ExpandExcerptDirection,
27249 },
27250 BufferEdited,
27251 Edited {
27252 transaction_id: clock::Lamport,
27253 },
27254 Reparsed(BufferId),
27255 Focused,
27256 FocusedIn,
27257 Blurred,
27258 DirtyChanged,
27259 Saved,
27260 TitleChanged,
27261 SelectionsChanged {
27262 local: bool,
27263 },
27264 ScrollPositionChanged {
27265 local: bool,
27266 autoscroll: bool,
27267 },
27268 TransactionUndone {
27269 transaction_id: clock::Lamport,
27270 },
27271 TransactionBegun {
27272 transaction_id: clock::Lamport,
27273 },
27274 CursorShapeChanged,
27275 BreadcrumbsChanged,
27276 PushedToNavHistory {
27277 anchor: Anchor,
27278 is_deactivate: bool,
27279 },
27280}
27281
27282impl EventEmitter<EditorEvent> for Editor {}
27283
27284impl Focusable for Editor {
27285 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27286 self.focus_handle.clone()
27287 }
27288}
27289
27290impl Render for Editor {
27291 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27292 EditorElement::new(&cx.entity(), self.create_style(cx))
27293 }
27294}
27295
27296impl EntityInputHandler for Editor {
27297 fn text_for_range(
27298 &mut self,
27299 range_utf16: Range<usize>,
27300 adjusted_range: &mut Option<Range<usize>>,
27301 _: &mut Window,
27302 cx: &mut Context<Self>,
27303 ) -> Option<String> {
27304 let snapshot = self.buffer.read(cx).read(cx);
27305 let start = snapshot.clip_offset_utf16(
27306 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27307 Bias::Left,
27308 );
27309 let end = snapshot.clip_offset_utf16(
27310 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27311 Bias::Right,
27312 );
27313 if (start.0.0..end.0.0) != range_utf16 {
27314 adjusted_range.replace(start.0.0..end.0.0);
27315 }
27316 Some(snapshot.text_for_range(start..end).collect())
27317 }
27318
27319 fn selected_text_range(
27320 &mut self,
27321 ignore_disabled_input: bool,
27322 _: &mut Window,
27323 cx: &mut Context<Self>,
27324 ) -> Option<UTF16Selection> {
27325 // Prevent the IME menu from appearing when holding down an alphabetic key
27326 // while input is disabled.
27327 if !ignore_disabled_input && !self.input_enabled {
27328 return None;
27329 }
27330
27331 let selection = self
27332 .selections
27333 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27334 let range = selection.range();
27335
27336 Some(UTF16Selection {
27337 range: range.start.0.0..range.end.0.0,
27338 reversed: selection.reversed,
27339 })
27340 }
27341
27342 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27343 let snapshot = self.buffer.read(cx).read(cx);
27344 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
27345 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27346 }
27347
27348 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27349 self.clear_highlights::<InputComposition>(cx);
27350 self.ime_transaction.take();
27351 }
27352
27353 fn replace_text_in_range(
27354 &mut self,
27355 range_utf16: Option<Range<usize>>,
27356 text: &str,
27357 window: &mut Window,
27358 cx: &mut Context<Self>,
27359 ) {
27360 if !self.input_enabled {
27361 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27362 return;
27363 }
27364
27365 self.transact(window, cx, |this, window, cx| {
27366 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27367 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27368 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27369 Some(this.selection_replacement_ranges(range_utf16, cx))
27370 } else {
27371 this.marked_text_ranges(cx)
27372 };
27373
27374 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27375 let newest_selection_id = this.selections.newest_anchor().id;
27376 this.selections
27377 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27378 .iter()
27379 .zip(ranges_to_replace.iter())
27380 .find_map(|(selection, range)| {
27381 if selection.id == newest_selection_id {
27382 Some(
27383 (range.start.0.0 as isize - selection.head().0.0 as isize)
27384 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27385 )
27386 } else {
27387 None
27388 }
27389 })
27390 });
27391
27392 cx.emit(EditorEvent::InputHandled {
27393 utf16_range_to_replace: range_to_replace,
27394 text: text.into(),
27395 });
27396
27397 if let Some(new_selected_ranges) = new_selected_ranges {
27398 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27399 selections.select_ranges(new_selected_ranges)
27400 });
27401 this.backspace(&Default::default(), window, cx);
27402 }
27403
27404 this.handle_input(text, window, cx);
27405 });
27406
27407 if let Some(transaction) = self.ime_transaction {
27408 self.buffer.update(cx, |buffer, cx| {
27409 buffer.group_until_transaction(transaction, cx);
27410 });
27411 }
27412
27413 self.unmark_text(window, cx);
27414 }
27415
27416 fn replace_and_mark_text_in_range(
27417 &mut self,
27418 range_utf16: Option<Range<usize>>,
27419 text: &str,
27420 new_selected_range_utf16: Option<Range<usize>>,
27421 window: &mut Window,
27422 cx: &mut Context<Self>,
27423 ) {
27424 if !self.input_enabled {
27425 return;
27426 }
27427
27428 let transaction = self.transact(window, cx, |this, window, cx| {
27429 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27430 let snapshot = this.buffer.read(cx).read(cx);
27431 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27432 for marked_range in &mut marked_ranges {
27433 marked_range.end = marked_range.start + relative_range_utf16.end;
27434 marked_range.start += relative_range_utf16.start;
27435 marked_range.start =
27436 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27437 marked_range.end =
27438 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27439 }
27440 }
27441 Some(marked_ranges)
27442 } else if let Some(range_utf16) = range_utf16 {
27443 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27444 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27445 Some(this.selection_replacement_ranges(range_utf16, cx))
27446 } else {
27447 None
27448 };
27449
27450 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27451 let newest_selection_id = this.selections.newest_anchor().id;
27452 this.selections
27453 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27454 .iter()
27455 .zip(ranges_to_replace.iter())
27456 .find_map(|(selection, range)| {
27457 if selection.id == newest_selection_id {
27458 Some(
27459 (range.start.0.0 as isize - selection.head().0.0 as isize)
27460 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27461 )
27462 } else {
27463 None
27464 }
27465 })
27466 });
27467
27468 cx.emit(EditorEvent::InputHandled {
27469 utf16_range_to_replace: range_to_replace,
27470 text: text.into(),
27471 });
27472
27473 if let Some(ranges) = ranges_to_replace {
27474 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27475 s.select_ranges(ranges)
27476 });
27477 }
27478
27479 let marked_ranges = {
27480 let snapshot = this.buffer.read(cx).read(cx);
27481 this.selections
27482 .disjoint_anchors_arc()
27483 .iter()
27484 .map(|selection| {
27485 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27486 })
27487 .collect::<Vec<_>>()
27488 };
27489
27490 if text.is_empty() {
27491 this.unmark_text(window, cx);
27492 } else {
27493 this.highlight_text::<InputComposition>(
27494 marked_ranges.clone(),
27495 HighlightStyle {
27496 underline: Some(UnderlineStyle {
27497 thickness: px(1.),
27498 color: None,
27499 wavy: false,
27500 }),
27501 ..Default::default()
27502 },
27503 cx,
27504 );
27505 }
27506
27507 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27508 let use_autoclose = this.use_autoclose;
27509 let use_auto_surround = this.use_auto_surround;
27510 this.set_use_autoclose(false);
27511 this.set_use_auto_surround(false);
27512 this.handle_input(text, window, cx);
27513 this.set_use_autoclose(use_autoclose);
27514 this.set_use_auto_surround(use_auto_surround);
27515
27516 if let Some(new_selected_range) = new_selected_range_utf16 {
27517 let snapshot = this.buffer.read(cx).read(cx);
27518 let new_selected_ranges = marked_ranges
27519 .into_iter()
27520 .map(|marked_range| {
27521 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27522 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27523 insertion_start.0 + new_selected_range.start,
27524 ));
27525 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27526 insertion_start.0 + new_selected_range.end,
27527 ));
27528 snapshot.clip_offset_utf16(new_start, Bias::Left)
27529 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27530 })
27531 .collect::<Vec<_>>();
27532
27533 drop(snapshot);
27534 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27535 selections.select_ranges(new_selected_ranges)
27536 });
27537 }
27538 });
27539
27540 self.ime_transaction = self.ime_transaction.or(transaction);
27541 if let Some(transaction) = self.ime_transaction {
27542 self.buffer.update(cx, |buffer, cx| {
27543 buffer.group_until_transaction(transaction, cx);
27544 });
27545 }
27546
27547 if self.text_highlights::<InputComposition>(cx).is_none() {
27548 self.ime_transaction.take();
27549 }
27550 }
27551
27552 fn bounds_for_range(
27553 &mut self,
27554 range_utf16: Range<usize>,
27555 element_bounds: gpui::Bounds<Pixels>,
27556 window: &mut Window,
27557 cx: &mut Context<Self>,
27558 ) -> Option<gpui::Bounds<Pixels>> {
27559 let text_layout_details = self.text_layout_details(window);
27560 let CharacterDimensions {
27561 em_width,
27562 em_advance,
27563 line_height,
27564 } = self.character_dimensions(window);
27565
27566 let snapshot = self.snapshot(window, cx);
27567 let scroll_position = snapshot.scroll_position();
27568 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
27569
27570 let start =
27571 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
27572 let x = Pixels::from(
27573 ScrollOffset::from(
27574 snapshot.x_for_display_point(start, &text_layout_details)
27575 + self.gutter_dimensions.full_width(),
27576 ) - scroll_left,
27577 );
27578 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
27579
27580 Some(Bounds {
27581 origin: element_bounds.origin + point(x, y),
27582 size: size(em_width, line_height),
27583 })
27584 }
27585
27586 fn character_index_for_point(
27587 &mut self,
27588 point: gpui::Point<Pixels>,
27589 _window: &mut Window,
27590 _cx: &mut Context<Self>,
27591 ) -> Option<usize> {
27592 let position_map = self.last_position_map.as_ref()?;
27593 if !position_map.text_hitbox.contains(&point) {
27594 return None;
27595 }
27596 let display_point = position_map.point_for_position(point).previous_valid;
27597 let anchor = position_map
27598 .snapshot
27599 .display_point_to_anchor(display_point, Bias::Left);
27600 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
27601 Some(utf16_offset.0.0)
27602 }
27603
27604 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
27605 self.input_enabled
27606 }
27607}
27608
27609trait SelectionExt {
27610 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
27611 fn spanned_rows(
27612 &self,
27613 include_end_if_at_line_start: bool,
27614 map: &DisplaySnapshot,
27615 ) -> Range<MultiBufferRow>;
27616}
27617
27618impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
27619 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
27620 let start = self
27621 .start
27622 .to_point(map.buffer_snapshot())
27623 .to_display_point(map);
27624 let end = self
27625 .end
27626 .to_point(map.buffer_snapshot())
27627 .to_display_point(map);
27628 if self.reversed {
27629 end..start
27630 } else {
27631 start..end
27632 }
27633 }
27634
27635 fn spanned_rows(
27636 &self,
27637 include_end_if_at_line_start: bool,
27638 map: &DisplaySnapshot,
27639 ) -> Range<MultiBufferRow> {
27640 let start = self.start.to_point(map.buffer_snapshot());
27641 let mut end = self.end.to_point(map.buffer_snapshot());
27642 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
27643 end.row -= 1;
27644 }
27645
27646 let buffer_start = map.prev_line_boundary(start).0;
27647 let buffer_end = map.next_line_boundary(end).0;
27648 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
27649 }
27650}
27651
27652impl<T: InvalidationRegion> InvalidationStack<T> {
27653 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
27654 where
27655 S: Clone + ToOffset,
27656 {
27657 while let Some(region) = self.last() {
27658 let all_selections_inside_invalidation_ranges =
27659 if selections.len() == region.ranges().len() {
27660 selections
27661 .iter()
27662 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
27663 .all(|(selection, invalidation_range)| {
27664 let head = selection.head().to_offset(buffer);
27665 invalidation_range.start <= head && invalidation_range.end >= head
27666 })
27667 } else {
27668 false
27669 };
27670
27671 if all_selections_inside_invalidation_ranges {
27672 break;
27673 } else {
27674 self.pop();
27675 }
27676 }
27677 }
27678}
27679
27680#[derive(Clone)]
27681struct ErasedEditorImpl(Entity<Editor>);
27682
27683impl ui_input::ErasedEditor for ErasedEditorImpl {
27684 fn text(&self, cx: &App) -> String {
27685 self.0.read(cx).text(cx)
27686 }
27687
27688 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
27689 self.0.update(cx, |this, cx| {
27690 this.set_text(text, window, cx);
27691 })
27692 }
27693
27694 fn clear(&self, window: &mut Window, cx: &mut App) {
27695 self.0.update(cx, |this, cx| this.clear(window, cx));
27696 }
27697
27698 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
27699 self.0.update(cx, |this, cx| {
27700 this.set_placeholder_text(text, window, cx);
27701 });
27702 }
27703
27704 fn focus_handle(&self, cx: &App) -> FocusHandle {
27705 self.0.read(cx).focus_handle(cx)
27706 }
27707
27708 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
27709 let settings = ThemeSettings::get_global(cx);
27710 let theme_color = cx.theme().colors();
27711
27712 let text_style = TextStyle {
27713 font_family: settings.ui_font.family.clone(),
27714 font_features: settings.ui_font.features.clone(),
27715 font_size: rems(0.875).into(),
27716 font_weight: settings.buffer_font.weight,
27717 font_style: FontStyle::Normal,
27718 line_height: relative(1.2),
27719 color: theme_color.text,
27720 ..Default::default()
27721 };
27722 let editor_style = EditorStyle {
27723 background: theme_color.ghost_element_background,
27724 local_player: cx.theme().players().local(),
27725 syntax: cx.theme().syntax().clone(),
27726 text: text_style,
27727 ..Default::default()
27728 };
27729 EditorElement::new(&self.0, editor_style).into_any()
27730 }
27731
27732 fn as_any(&self) -> &dyn Any {
27733 &self.0
27734 }
27735
27736 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
27737 self.0.update(cx, |editor, cx| {
27738 let editor_offset = editor.buffer().read(cx).len(cx);
27739 editor.change_selections(
27740 SelectionEffects::scroll(Autoscroll::Next),
27741 window,
27742 cx,
27743 |s| s.select_ranges(Some(editor_offset..editor_offset)),
27744 );
27745 });
27746 }
27747
27748 fn subscribe(
27749 &self,
27750 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
27751 window: &mut Window,
27752 cx: &mut App,
27753 ) -> Subscription {
27754 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
27755 let event = match event {
27756 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
27757 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
27758 _ => return,
27759 };
27760 (callback)(event, window, cx);
27761 })
27762 }
27763}
27764impl<T> Default for InvalidationStack<T> {
27765 fn default() -> Self {
27766 Self(Default::default())
27767 }
27768}
27769
27770impl<T> Deref for InvalidationStack<T> {
27771 type Target = Vec<T>;
27772
27773 fn deref(&self) -> &Self::Target {
27774 &self.0
27775 }
27776}
27777
27778impl<T> DerefMut for InvalidationStack<T> {
27779 fn deref_mut(&mut self) -> &mut Self::Target {
27780 &mut self.0
27781 }
27782}
27783
27784impl InvalidationRegion for SnippetState {
27785 fn ranges(&self) -> &[Range<Anchor>] {
27786 &self.ranges[self.active_index]
27787 }
27788}
27789
27790fn edit_prediction_edit_text(
27791 current_snapshot: &BufferSnapshot,
27792 edits: &[(Range<Anchor>, impl AsRef<str>)],
27793 edit_preview: &EditPreview,
27794 include_deletions: bool,
27795 cx: &App,
27796) -> HighlightedText {
27797 let edits = edits
27798 .iter()
27799 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
27800 .collect::<Vec<_>>();
27801
27802 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
27803}
27804
27805fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
27806 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
27807 // Just show the raw edit text with basic styling
27808 let mut text = String::new();
27809 let mut highlights = Vec::new();
27810
27811 let insertion_highlight_style = HighlightStyle {
27812 color: Some(cx.theme().colors().text),
27813 ..Default::default()
27814 };
27815
27816 for (_, edit_text) in edits {
27817 let start_offset = text.len();
27818 text.push_str(edit_text);
27819 let end_offset = text.len();
27820
27821 if start_offset < end_offset {
27822 highlights.push((start_offset..end_offset, insertion_highlight_style));
27823 }
27824 }
27825
27826 HighlightedText {
27827 text: text.into(),
27828 highlights,
27829 }
27830}
27831
27832pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
27833 match severity {
27834 lsp::DiagnosticSeverity::ERROR => colors.error,
27835 lsp::DiagnosticSeverity::WARNING => colors.warning,
27836 lsp::DiagnosticSeverity::INFORMATION => colors.info,
27837 lsp::DiagnosticSeverity::HINT => colors.info,
27838 _ => colors.ignored,
27839 }
27840}
27841
27842pub fn styled_runs_for_code_label<'a>(
27843 label: &'a CodeLabel,
27844 syntax_theme: &'a theme::SyntaxTheme,
27845 local_player: &'a theme::PlayerColor,
27846) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
27847 let fade_out = HighlightStyle {
27848 fade_out: Some(0.35),
27849 ..Default::default()
27850 };
27851
27852 let mut prev_end = label.filter_range.end;
27853 label
27854 .runs
27855 .iter()
27856 .enumerate()
27857 .flat_map(move |(ix, (range, highlight_id))| {
27858 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
27859 HighlightStyle {
27860 color: Some(local_player.cursor),
27861 ..Default::default()
27862 }
27863 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
27864 HighlightStyle {
27865 background_color: Some(local_player.selection),
27866 ..Default::default()
27867 }
27868 } else if let Some(style) = highlight_id.style(syntax_theme) {
27869 style
27870 } else {
27871 return Default::default();
27872 };
27873 let muted_style = style.highlight(fade_out);
27874
27875 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
27876 if range.start >= label.filter_range.end {
27877 if range.start > prev_end {
27878 runs.push((prev_end..range.start, fade_out));
27879 }
27880 runs.push((range.clone(), muted_style));
27881 } else if range.end <= label.filter_range.end {
27882 runs.push((range.clone(), style));
27883 } else {
27884 runs.push((range.start..label.filter_range.end, style));
27885 runs.push((label.filter_range.end..range.end, muted_style));
27886 }
27887 prev_end = cmp::max(prev_end, range.end);
27888
27889 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
27890 runs.push((prev_end..label.text.len(), fade_out));
27891 }
27892
27893 runs
27894 })
27895}
27896
27897pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
27898 let mut prev_index = 0;
27899 let mut prev_codepoint: Option<char> = None;
27900 text.char_indices()
27901 .chain([(text.len(), '\0')])
27902 .filter_map(move |(index, codepoint)| {
27903 let prev_codepoint = prev_codepoint.replace(codepoint)?;
27904 let is_boundary = index == text.len()
27905 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
27906 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
27907 if is_boundary {
27908 let chunk = &text[prev_index..index];
27909 prev_index = index;
27910 Some(chunk)
27911 } else {
27912 None
27913 }
27914 })
27915}
27916
27917/// Given a string of text immediately before the cursor, iterates over possible
27918/// strings a snippet could match to. More precisely: returns an iterator over
27919/// suffixes of `text` created by splitting at word boundaries (before & after
27920/// every non-word character).
27921///
27922/// Shorter suffixes are returned first.
27923pub(crate) fn snippet_candidate_suffixes(
27924 text: &str,
27925 is_word_char: impl Fn(char) -> bool,
27926) -> impl std::iter::Iterator<Item = &str> {
27927 let mut prev_index = text.len();
27928 let mut prev_codepoint = None;
27929 text.char_indices()
27930 .rev()
27931 .chain([(0, '\0')])
27932 .filter_map(move |(index, codepoint)| {
27933 let prev_index = std::mem::replace(&mut prev_index, index);
27934 let prev_codepoint = prev_codepoint.replace(codepoint)?;
27935 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
27936 None
27937 } else {
27938 let chunk = &text[prev_index..]; // go to end of string
27939 Some(chunk)
27940 }
27941 })
27942}
27943
27944pub trait RangeToAnchorExt: Sized {
27945 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
27946
27947 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
27948 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
27949 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
27950 }
27951}
27952
27953impl<T: ToOffset> RangeToAnchorExt for Range<T> {
27954 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
27955 let start_offset = self.start.to_offset(snapshot);
27956 let end_offset = self.end.to_offset(snapshot);
27957 if start_offset == end_offset {
27958 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
27959 } else {
27960 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
27961 }
27962 }
27963}
27964
27965pub trait RowExt {
27966 fn as_f64(&self) -> f64;
27967
27968 fn next_row(&self) -> Self;
27969
27970 fn previous_row(&self) -> Self;
27971
27972 fn minus(&self, other: Self) -> u32;
27973}
27974
27975impl RowExt for DisplayRow {
27976 fn as_f64(&self) -> f64 {
27977 self.0 as _
27978 }
27979
27980 fn next_row(&self) -> Self {
27981 Self(self.0 + 1)
27982 }
27983
27984 fn previous_row(&self) -> Self {
27985 Self(self.0.saturating_sub(1))
27986 }
27987
27988 fn minus(&self, other: Self) -> u32 {
27989 self.0 - other.0
27990 }
27991}
27992
27993impl RowExt for MultiBufferRow {
27994 fn as_f64(&self) -> f64 {
27995 self.0 as _
27996 }
27997
27998 fn next_row(&self) -> Self {
27999 Self(self.0 + 1)
28000 }
28001
28002 fn previous_row(&self) -> Self {
28003 Self(self.0.saturating_sub(1))
28004 }
28005
28006 fn minus(&self, other: Self) -> u32 {
28007 self.0 - other.0
28008 }
28009}
28010
28011trait RowRangeExt {
28012 type Row;
28013
28014 fn len(&self) -> usize;
28015
28016 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28017}
28018
28019impl RowRangeExt for Range<MultiBufferRow> {
28020 type Row = MultiBufferRow;
28021
28022 fn len(&self) -> usize {
28023 (self.end.0 - self.start.0) as usize
28024 }
28025
28026 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28027 (self.start.0..self.end.0).map(MultiBufferRow)
28028 }
28029}
28030
28031impl RowRangeExt for Range<DisplayRow> {
28032 type Row = DisplayRow;
28033
28034 fn len(&self) -> usize {
28035 (self.end.0 - self.start.0) as usize
28036 }
28037
28038 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28039 (self.start.0..self.end.0).map(DisplayRow)
28040 }
28041}
28042
28043/// If select range has more than one line, we
28044/// just point the cursor to range.start.
28045fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28046 if range.start.row == range.end.row {
28047 range
28048 } else {
28049 range.start..range.start
28050 }
28051}
28052pub struct KillRing(ClipboardItem);
28053impl Global for KillRing {}
28054
28055const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28056
28057enum BreakpointPromptEditAction {
28058 Log,
28059 Condition,
28060 HitCondition,
28061}
28062
28063struct BreakpointPromptEditor {
28064 pub(crate) prompt: Entity<Editor>,
28065 editor: WeakEntity<Editor>,
28066 breakpoint_anchor: Anchor,
28067 breakpoint: Breakpoint,
28068 edit_action: BreakpointPromptEditAction,
28069 block_ids: HashSet<CustomBlockId>,
28070 editor_margins: Arc<Mutex<EditorMargins>>,
28071 _subscriptions: Vec<Subscription>,
28072}
28073
28074impl BreakpointPromptEditor {
28075 const MAX_LINES: u8 = 4;
28076
28077 fn new(
28078 editor: WeakEntity<Editor>,
28079 breakpoint_anchor: Anchor,
28080 breakpoint: Breakpoint,
28081 edit_action: BreakpointPromptEditAction,
28082 window: &mut Window,
28083 cx: &mut Context<Self>,
28084 ) -> Self {
28085 let base_text = match edit_action {
28086 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28087 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28088 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28089 }
28090 .map(|msg| msg.to_string())
28091 .unwrap_or_default();
28092
28093 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28094 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28095
28096 let prompt = cx.new(|cx| {
28097 let mut prompt = Editor::new(
28098 EditorMode::AutoHeight {
28099 min_lines: 1,
28100 max_lines: Some(Self::MAX_LINES as usize),
28101 },
28102 buffer,
28103 None,
28104 window,
28105 cx,
28106 );
28107 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28108 prompt.set_show_cursor_when_unfocused(false, cx);
28109 prompt.set_placeholder_text(
28110 match edit_action {
28111 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28112 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28113 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28114 },
28115 window,
28116 cx,
28117 );
28118
28119 prompt
28120 });
28121
28122 Self {
28123 prompt,
28124 editor,
28125 breakpoint_anchor,
28126 breakpoint,
28127 edit_action,
28128 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28129 block_ids: Default::default(),
28130 _subscriptions: vec![],
28131 }
28132 }
28133
28134 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28135 self.block_ids.extend(block_ids)
28136 }
28137
28138 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28139 if let Some(editor) = self.editor.upgrade() {
28140 let message = self
28141 .prompt
28142 .read(cx)
28143 .buffer
28144 .read(cx)
28145 .as_singleton()
28146 .expect("A multi buffer in breakpoint prompt isn't possible")
28147 .read(cx)
28148 .as_rope()
28149 .to_string();
28150
28151 editor.update(cx, |editor, cx| {
28152 editor.edit_breakpoint_at_anchor(
28153 self.breakpoint_anchor,
28154 self.breakpoint.clone(),
28155 match self.edit_action {
28156 BreakpointPromptEditAction::Log => {
28157 BreakpointEditAction::EditLogMessage(message.into())
28158 }
28159 BreakpointPromptEditAction::Condition => {
28160 BreakpointEditAction::EditCondition(message.into())
28161 }
28162 BreakpointPromptEditAction::HitCondition => {
28163 BreakpointEditAction::EditHitCondition(message.into())
28164 }
28165 },
28166 cx,
28167 );
28168
28169 editor.remove_blocks(self.block_ids.clone(), None, cx);
28170 cx.focus_self(window);
28171 });
28172 }
28173 }
28174
28175 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28176 self.editor
28177 .update(cx, |editor, cx| {
28178 editor.remove_blocks(self.block_ids.clone(), None, cx);
28179 window.focus(&editor.focus_handle, cx);
28180 })
28181 .log_err();
28182 }
28183
28184 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28185 let settings = ThemeSettings::get_global(cx);
28186 let text_style = TextStyle {
28187 color: if self.prompt.read(cx).read_only(cx) {
28188 cx.theme().colors().text_disabled
28189 } else {
28190 cx.theme().colors().text
28191 },
28192 font_family: settings.buffer_font.family.clone(),
28193 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28194 font_size: settings.buffer_font_size(cx).into(),
28195 font_weight: settings.buffer_font.weight,
28196 line_height: relative(settings.buffer_line_height.value()),
28197 ..Default::default()
28198 };
28199 EditorElement::new(
28200 &self.prompt,
28201 EditorStyle {
28202 background: cx.theme().colors().editor_background,
28203 local_player: cx.theme().players().local(),
28204 text: text_style,
28205 ..Default::default()
28206 },
28207 )
28208 }
28209}
28210
28211impl Render for BreakpointPromptEditor {
28212 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28213 let editor_margins = *self.editor_margins.lock();
28214 let gutter_dimensions = editor_margins.gutter;
28215 h_flex()
28216 .key_context("Editor")
28217 .bg(cx.theme().colors().editor_background)
28218 .border_y_1()
28219 .border_color(cx.theme().status().info_border)
28220 .size_full()
28221 .py(window.line_height() / 2.5)
28222 .on_action(cx.listener(Self::confirm))
28223 .on_action(cx.listener(Self::cancel))
28224 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28225 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28226 }
28227}
28228
28229impl Focusable for BreakpointPromptEditor {
28230 fn focus_handle(&self, cx: &App) -> FocusHandle {
28231 self.prompt.focus_handle(cx)
28232 }
28233}
28234
28235fn all_edits_insertions_or_deletions(
28236 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28237 snapshot: &MultiBufferSnapshot,
28238) -> bool {
28239 let mut all_insertions = true;
28240 let mut all_deletions = true;
28241
28242 for (range, new_text) in edits.iter() {
28243 let range_is_empty = range.to_offset(snapshot).is_empty();
28244 let text_is_empty = new_text.is_empty();
28245
28246 if range_is_empty != text_is_empty {
28247 if range_is_empty {
28248 all_deletions = false;
28249 } else {
28250 all_insertions = false;
28251 }
28252 } else {
28253 return false;
28254 }
28255
28256 if !all_insertions && !all_deletions {
28257 return false;
28258 }
28259 }
28260 all_insertions || all_deletions
28261}
28262
28263struct MissingEditPredictionKeybindingTooltip;
28264
28265impl Render for MissingEditPredictionKeybindingTooltip {
28266 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28267 ui::tooltip_container(cx, |container, cx| {
28268 container
28269 .flex_shrink_0()
28270 .max_w_80()
28271 .min_h(rems_from_px(124.))
28272 .justify_between()
28273 .child(
28274 v_flex()
28275 .flex_1()
28276 .text_ui_sm(cx)
28277 .child(Label::new("Conflict with Accept Keybinding"))
28278 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28279 )
28280 .child(
28281 h_flex()
28282 .pb_1()
28283 .gap_1()
28284 .items_end()
28285 .w_full()
28286 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28287 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28288 }))
28289 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28290 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28291 })),
28292 )
28293 })
28294 }
28295}
28296
28297#[derive(Debug, Clone, Copy, PartialEq)]
28298pub struct LineHighlight {
28299 pub background: Background,
28300 pub border: Option<gpui::Hsla>,
28301 pub include_gutter: bool,
28302 pub type_id: Option<TypeId>,
28303}
28304
28305struct LineManipulationResult {
28306 pub new_text: String,
28307 pub line_count_before: usize,
28308 pub line_count_after: usize,
28309}
28310
28311fn render_diff_hunk_controls(
28312 row: u32,
28313 status: &DiffHunkStatus,
28314 hunk_range: Range<Anchor>,
28315 is_created_file: bool,
28316 line_height: Pixels,
28317 editor: &Entity<Editor>,
28318 _window: &mut Window,
28319 cx: &mut App,
28320) -> AnyElement {
28321 h_flex()
28322 .h(line_height)
28323 .mr_1()
28324 .gap_1()
28325 .px_0p5()
28326 .pb_1()
28327 .border_x_1()
28328 .border_b_1()
28329 .border_color(cx.theme().colors().border_variant)
28330 .rounded_b_lg()
28331 .bg(cx.theme().colors().editor_background)
28332 .gap_1()
28333 .block_mouse_except_scroll()
28334 .shadow_md()
28335 .child(if status.has_secondary_hunk() {
28336 Button::new(("stage", row as u64), "Stage")
28337 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28338 .tooltip({
28339 let focus_handle = editor.focus_handle(cx);
28340 move |_window, cx| {
28341 Tooltip::for_action_in(
28342 "Stage Hunk",
28343 &::git::ToggleStaged,
28344 &focus_handle,
28345 cx,
28346 )
28347 }
28348 })
28349 .on_click({
28350 let editor = editor.clone();
28351 move |_event, _window, cx| {
28352 editor.update(cx, |editor, cx| {
28353 editor.stage_or_unstage_diff_hunks(
28354 true,
28355 vec![hunk_range.start..hunk_range.start],
28356 cx,
28357 );
28358 });
28359 }
28360 })
28361 } else {
28362 Button::new(("unstage", row as u64), "Unstage")
28363 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28364 .tooltip({
28365 let focus_handle = editor.focus_handle(cx);
28366 move |_window, cx| {
28367 Tooltip::for_action_in(
28368 "Unstage Hunk",
28369 &::git::ToggleStaged,
28370 &focus_handle,
28371 cx,
28372 )
28373 }
28374 })
28375 .on_click({
28376 let editor = editor.clone();
28377 move |_event, _window, cx| {
28378 editor.update(cx, |editor, cx| {
28379 editor.stage_or_unstage_diff_hunks(
28380 false,
28381 vec![hunk_range.start..hunk_range.start],
28382 cx,
28383 );
28384 });
28385 }
28386 })
28387 })
28388 .child(
28389 Button::new(("restore", row as u64), "Restore")
28390 .tooltip({
28391 let focus_handle = editor.focus_handle(cx);
28392 move |_window, cx| {
28393 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28394 }
28395 })
28396 .on_click({
28397 let editor = editor.clone();
28398 move |_event, window, cx| {
28399 editor.update(cx, |editor, cx| {
28400 let snapshot = editor.snapshot(window, cx);
28401 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28402 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28403 });
28404 }
28405 })
28406 .disabled(is_created_file),
28407 )
28408 .when(
28409 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28410 |el| {
28411 el.child(
28412 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28413 .shape(IconButtonShape::Square)
28414 .icon_size(IconSize::Small)
28415 // .disabled(!has_multiple_hunks)
28416 .tooltip({
28417 let focus_handle = editor.focus_handle(cx);
28418 move |_window, cx| {
28419 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28420 }
28421 })
28422 .on_click({
28423 let editor = editor.clone();
28424 move |_event, window, cx| {
28425 editor.update(cx, |editor, cx| {
28426 let snapshot = editor.snapshot(window, cx);
28427 let position =
28428 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28429 editor.go_to_hunk_before_or_after_position(
28430 &snapshot,
28431 position,
28432 Direction::Next,
28433 window,
28434 cx,
28435 );
28436 editor.expand_selected_diff_hunks(cx);
28437 });
28438 }
28439 }),
28440 )
28441 .child(
28442 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28443 .shape(IconButtonShape::Square)
28444 .icon_size(IconSize::Small)
28445 // .disabled(!has_multiple_hunks)
28446 .tooltip({
28447 let focus_handle = editor.focus_handle(cx);
28448 move |_window, cx| {
28449 Tooltip::for_action_in(
28450 "Previous Hunk",
28451 &GoToPreviousHunk,
28452 &focus_handle,
28453 cx,
28454 )
28455 }
28456 })
28457 .on_click({
28458 let editor = editor.clone();
28459 move |_event, window, cx| {
28460 editor.update(cx, |editor, cx| {
28461 let snapshot = editor.snapshot(window, cx);
28462 let point =
28463 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28464 editor.go_to_hunk_before_or_after_position(
28465 &snapshot,
28466 point,
28467 Direction::Prev,
28468 window,
28469 cx,
28470 );
28471 editor.expand_selected_diff_hunks(cx);
28472 });
28473 }
28474 }),
28475 )
28476 },
28477 )
28478 .into_any_element()
28479}
28480
28481pub fn multibuffer_context_lines(cx: &App) -> u32 {
28482 EditorSettings::try_get(cx)
28483 .map(|settings| settings.excerpt_context_lines)
28484 .unwrap_or(2)
28485 .min(32)
28486}