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 tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction_types::Direction;
55pub use editor_settings::{
56 CompletionDetailAlignment, CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings,
57 HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61 render_breadcrumb_text,
62};
63pub use git::blame::BlameRenderer;
64pub use hover_popover::hover_markdown_style;
65pub use inlays::Inlay;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
71 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
72 ToPoint,
73};
74pub use split::SplittableEditor;
75pub use text::Bias;
76
77use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
78use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
79use anyhow::{Context as _, Result, anyhow, bail};
80use blink_manager::BlinkManager;
81use buffer_diff::DiffHunkStatus;
82use client::{Collaborator, ParticipantIndex, parse_zed_link};
83use clock::ReplicaId;
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use collections::{BTreeMap, HashMap, HashSet, VecDeque};
89use convert_case::{Case, Casing};
90use dap::TelemetrySpawnLocation;
91use display_map::*;
92use edit_prediction_types::{
93 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
94 SuggestionDisplayType,
95};
96use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
97use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
98use futures::{
99 FutureExt, StreamExt as _,
100 future::{self, Shared, join},
101 stream::FuturesUnordered,
102};
103use fuzzy::{StringMatch, StringMatchCandidate};
104use git::blame::{GitBlame, GlobalBlameRenderer};
105use gpui::{
106 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
107 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
108 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
109 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
110 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
111 Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled, Subscription, Task,
112 TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
113 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
114 pulsating_between, px, relative, size,
115};
116use hover_links::{HoverLink, HoveredLinkState, find_file};
117use hover_popover::{HoverState, hide_hover};
118use indent_guides::ActiveIndentGuidesState;
119use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
120use itertools::{Either, Itertools};
121use language::{
122 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
123 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
124 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
125 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
126 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
127 TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145};
146use parking_lot::Mutex;
147use persistence::DB;
148use project::{
149 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
150 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
151 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
152 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::{
154 breakpoint_store::{
155 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
156 BreakpointStore, BreakpointStoreEvent,
157 },
158 session::{Session, SessionEvent},
159 },
160 git_store::GitStoreEvent,
161 lsp_store::{
162 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
163 OpenLspBufferHandle,
164 },
165 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
166};
167use rand::seq::SliceRandom;
168use regex::Regex;
169use rpc::{ErrorCode, ErrorExt, proto::PeerId};
170use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
171use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
172use serde::{Deserialize, Serialize};
173use settings::{
174 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
175 update_settings_file,
176};
177use smallvec::{SmallVec, smallvec};
178use snippet::Snippet;
179use std::{
180 any::{Any, TypeId},
181 borrow::Cow,
182 cell::{OnceCell, RefCell},
183 cmp::{self, Ordering, Reverse},
184 collections::hash_map,
185 iter::{self, Peekable},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 sync::Arc,
192 time::{Duration, Instant},
193};
194use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
196use theme::{
197 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
202 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
207 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
208 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::{CollapseDirection, SearchEvent},
212};
213
214use crate::{
215 code_context_menus::CompletionsMenuSource,
216 editor_settings::MultiCursorModifier,
217 hover_links::{find_url, find_url_from_range},
218 inlays::{
219 InlineValueCache,
220 inlay_hints::{LspInlayHintData, inlay_hint_settings},
221 },
222 scroll::{ScrollOffset, ScrollPixelOffset},
223 selections_collection::resolve_selections_wrapping_blocks,
224 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
225};
226
227pub const FILE_HEADER_HEIGHT: u32 = 2;
228pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
242
243pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
244pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
245pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
246
247pub type RenderDiffHunkControlsFn = Arc<
248 dyn Fn(
249 u32,
250 &DiffHunkStatus,
251 Range<Anchor>,
252 bool,
253 Pixels,
254 &Entity<Editor>,
255 &mut Window,
256 &mut App,
257 ) -> AnyElement,
258>;
259
260enum ReportEditorEvent {
261 Saved { auto_saved: bool },
262 EditorOpened,
263 Closed,
264}
265
266impl ReportEditorEvent {
267 pub fn event_type(&self) -> &'static str {
268 match self {
269 Self::Saved { .. } => "Editor Saved",
270 Self::EditorOpened => "Editor Opened",
271 Self::Closed => "Editor Closed",
272 }
273 }
274}
275
276pub enum ActiveDebugLine {}
277pub enum DebugStackFrameLine {}
278enum DocumentHighlightRead {}
279enum DocumentHighlightWrite {}
280enum InputComposition {}
281pub enum PendingInput {}
282enum SelectedTextHighlight {}
283
284pub enum ConflictsOuter {}
285pub enum ConflictsOurs {}
286pub enum ConflictsTheirs {}
287pub enum ConflictsOursMarker {}
288pub enum ConflictsTheirsMarker {}
289
290pub struct HunkAddedColor;
291pub struct HunkRemovedColor;
292
293#[derive(Debug, Copy, Clone, PartialEq, Eq)]
294pub enum Navigated {
295 Yes,
296 No,
297}
298
299impl Navigated {
300 pub fn from_bool(yes: bool) -> Navigated {
301 if yes { Navigated::Yes } else { Navigated::No }
302 }
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
306enum DisplayDiffHunk {
307 Folded {
308 display_row: DisplayRow,
309 },
310 Unfolded {
311 is_created_file: bool,
312 diff_base_byte_range: Range<usize>,
313 display_row_range: Range<DisplayRow>,
314 multi_buffer_range: Range<Anchor>,
315 status: DiffHunkStatus,
316 word_diffs: Vec<Range<MultiBufferOffset>>,
317 },
318}
319
320pub enum HideMouseCursorOrigin {
321 TypingAction,
322 MovementAction,
323}
324
325pub fn init(cx: &mut App) {
326 cx.set_global(GlobalBlameRenderer(Arc::new(())));
327
328 workspace::register_project_item::<Editor>(cx);
329 workspace::FollowableViewRegistry::register::<Editor>(cx);
330 workspace::register_serializable_item::<Editor>(cx);
331
332 cx.observe_new(
333 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
334 workspace.register_action(Editor::new_file);
335 workspace.register_action(Editor::new_file_split);
336 workspace.register_action(Editor::new_file_vertical);
337 workspace.register_action(Editor::new_file_horizontal);
338 workspace.register_action(Editor::cancel_language_server_work);
339 workspace.register_action(Editor::toggle_focus);
340 },
341 )
342 .detach();
343
344 cx.on_action(move |_: &workspace::NewFile, cx| {
345 let app_state = workspace::AppState::global(cx);
346 if let Some(app_state) = app_state.upgrade() {
347 workspace::open_new(
348 Default::default(),
349 app_state,
350 cx,
351 |workspace, window, cx| {
352 Editor::new_file(workspace, &Default::default(), window, cx)
353 },
354 )
355 .detach();
356 }
357 })
358 .on_action(move |_: &workspace::NewWindow, cx| {
359 let app_state = workspace::AppState::global(cx);
360 if let Some(app_state) = app_state.upgrade() {
361 workspace::open_new(
362 Default::default(),
363 app_state,
364 cx,
365 |workspace, window, cx| {
366 cx.activate(true);
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373}
374
375pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
376 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
377}
378
379pub trait DiagnosticRenderer {
380 fn render_group(
381 &self,
382 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
383 buffer_id: BufferId,
384 snapshot: EditorSnapshot,
385 editor: WeakEntity<Editor>,
386 language_registry: Option<Arc<LanguageRegistry>>,
387 cx: &mut App,
388 ) -> Vec<BlockProperties<Anchor>>;
389
390 fn render_hover(
391 &self,
392 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
393 range: Range<Point>,
394 buffer_id: BufferId,
395 language_registry: Option<Arc<LanguageRegistry>>,
396 cx: &mut App,
397 ) -> Option<Entity<markdown::Markdown>>;
398
399 fn open_link(
400 &self,
401 editor: &mut Editor,
402 link: SharedString,
403 window: &mut Window,
404 cx: &mut Context<Editor>,
405 );
406}
407
408pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
409
410impl GlobalDiagnosticRenderer {
411 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
412 cx.try_global::<Self>().map(|g| g.0.clone())
413 }
414}
415
416impl gpui::Global for GlobalDiagnosticRenderer {}
417pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
418 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
419}
420
421pub struct SearchWithinRange;
422
423trait InvalidationRegion {
424 fn ranges(&self) -> &[Range<Anchor>];
425}
426
427#[derive(Clone, Debug, PartialEq)]
428pub enum SelectPhase {
429 Begin {
430 position: DisplayPoint,
431 add: bool,
432 click_count: usize,
433 },
434 BeginColumnar {
435 position: DisplayPoint,
436 reset: bool,
437 mode: ColumnarMode,
438 goal_column: u32,
439 },
440 Extend {
441 position: DisplayPoint,
442 click_count: usize,
443 },
444 Update {
445 position: DisplayPoint,
446 goal_column: u32,
447 scroll_delta: gpui::Point<f32>,
448 },
449 End,
450}
451
452#[derive(Clone, Debug, PartialEq)]
453pub enum ColumnarMode {
454 FromMouse,
455 FromSelection,
456}
457
458#[derive(Clone, Debug)]
459pub enum SelectMode {
460 Character,
461 Word(Range<Anchor>),
462 Line(Range<Anchor>),
463 All,
464}
465
466#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
467pub enum SizingBehavior {
468 /// The editor will layout itself using `size_full` and will include the vertical
469 /// scroll margin as requested by user settings.
470 #[default]
471 Default,
472 /// The editor will layout itself using `size_full`, but will not have any
473 /// vertical overscroll.
474 ExcludeOverscrollMargin,
475 /// The editor will request a vertical size according to its content and will be
476 /// layouted without a vertical scroll margin.
477 SizeByContent,
478}
479
480#[derive(Clone, PartialEq, Eq, Debug)]
481pub enum EditorMode {
482 SingleLine,
483 AutoHeight {
484 min_lines: usize,
485 max_lines: Option<usize>,
486 },
487 Full {
488 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
489 scale_ui_elements_with_buffer_font_size: bool,
490 /// When set to `true`, the editor will render a background for the active line.
491 show_active_line_background: bool,
492 /// Determines the sizing behavior for this editor
493 sizing_behavior: SizingBehavior,
494 },
495 Minimap {
496 parent: WeakEntity<Editor>,
497 },
498}
499
500impl EditorMode {
501 pub fn full() -> Self {
502 Self::Full {
503 scale_ui_elements_with_buffer_font_size: true,
504 show_active_line_background: true,
505 sizing_behavior: SizingBehavior::Default,
506 }
507 }
508
509 #[inline]
510 pub fn is_full(&self) -> bool {
511 matches!(self, Self::Full { .. })
512 }
513
514 #[inline]
515 pub fn is_single_line(&self) -> bool {
516 matches!(self, Self::SingleLine { .. })
517 }
518
519 #[inline]
520 fn is_minimap(&self) -> bool {
521 matches!(self, Self::Minimap { .. })
522 }
523}
524
525#[derive(Copy, Clone, Debug)]
526pub enum SoftWrap {
527 /// Prefer not to wrap at all.
528 ///
529 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
530 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
531 GitDiff,
532 /// Prefer a single line generally, unless an overly long line is encountered.
533 None,
534 /// Soft wrap lines that exceed the editor width.
535 EditorWidth,
536 /// Soft wrap lines at the preferred line length.
537 Column(u32),
538 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
539 Bounded(u32),
540}
541
542#[derive(Clone)]
543pub struct EditorStyle {
544 pub background: Hsla,
545 pub border: Hsla,
546 pub local_player: PlayerColor,
547 pub text: TextStyle,
548 pub scrollbar_width: Pixels,
549 pub syntax: Arc<SyntaxTheme>,
550 pub status: StatusColors,
551 pub inlay_hints_style: HighlightStyle,
552 pub edit_prediction_styles: EditPredictionStyles,
553 pub unnecessary_code_fade: f32,
554 pub show_underlines: bool,
555}
556
557impl Default for EditorStyle {
558 fn default() -> Self {
559 Self {
560 background: Hsla::default(),
561 border: Hsla::default(),
562 local_player: PlayerColor::default(),
563 text: TextStyle::default(),
564 scrollbar_width: Pixels::default(),
565 syntax: Default::default(),
566 // HACK: Status colors don't have a real default.
567 // We should look into removing the status colors from the editor
568 // style and retrieve them directly from the theme.
569 status: StatusColors::dark(),
570 inlay_hints_style: HighlightStyle::default(),
571 edit_prediction_styles: EditPredictionStyles {
572 insertion: HighlightStyle::default(),
573 whitespace: HighlightStyle::default(),
574 },
575 unnecessary_code_fade: Default::default(),
576 show_underlines: true,
577 }
578 }
579}
580
581pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
582 let show_background = language_settings::language_settings(None, None, cx)
583 .inlay_hints
584 .show_background;
585
586 let mut style = cx.theme().syntax().get("hint");
587
588 if style.color.is_none() {
589 style.color = Some(cx.theme().status().hint);
590 }
591
592 if !show_background {
593 style.background_color = None;
594 return style;
595 }
596
597 if style.background_color.is_none() {
598 style.background_color = Some(cx.theme().status().hint_background);
599 }
600
601 style
602}
603
604pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
605 EditPredictionStyles {
606 insertion: HighlightStyle {
607 color: Some(cx.theme().status().predictive),
608 ..HighlightStyle::default()
609 },
610 whitespace: HighlightStyle {
611 background_color: Some(cx.theme().status().created_background),
612 ..HighlightStyle::default()
613 },
614 }
615}
616
617type CompletionId = usize;
618
619pub(crate) enum EditDisplayMode {
620 TabAccept,
621 DiffPopover,
622 Inline,
623}
624
625enum EditPrediction {
626 Edit {
627 edits: Vec<(Range<Anchor>, Arc<str>)>,
628 edit_preview: Option<EditPreview>,
629 display_mode: EditDisplayMode,
630 snapshot: BufferSnapshot,
631 },
632 /// Move to a specific location in the active editor
633 MoveWithin {
634 target: Anchor,
635 snapshot: BufferSnapshot,
636 },
637 /// Move to a specific location in a different editor (not the active one)
638 MoveOutside {
639 target: language::Anchor,
640 snapshot: BufferSnapshot,
641 },
642}
643
644struct EditPredictionState {
645 inlay_ids: Vec<InlayId>,
646 completion: EditPrediction,
647 completion_id: Option<SharedString>,
648 invalidation_range: Option<Range<Anchor>>,
649}
650
651enum EditPredictionSettings {
652 Disabled,
653 Enabled {
654 show_in_menu: bool,
655 preview_requires_modifier: bool,
656 },
657}
658
659enum EditPredictionHighlight {}
660
661#[derive(Debug, Clone)]
662struct InlineDiagnostic {
663 message: SharedString,
664 group_id: usize,
665 is_primary: bool,
666 start: Point,
667 severity: lsp::DiagnosticSeverity,
668}
669
670pub enum MenuEditPredictionsPolicy {
671 Never,
672 ByProvider,
673}
674
675pub enum EditPredictionPreview {
676 /// Modifier is not pressed
677 Inactive { released_too_fast: bool },
678 /// Modifier pressed
679 Active {
680 since: Instant,
681 previous_scroll_position: Option<ScrollAnchor>,
682 },
683}
684
685impl EditPredictionPreview {
686 pub fn released_too_fast(&self) -> bool {
687 match self {
688 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
689 EditPredictionPreview::Active { .. } => false,
690 }
691 }
692
693 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
694 if let EditPredictionPreview::Active {
695 previous_scroll_position,
696 ..
697 } = self
698 {
699 *previous_scroll_position = scroll_position;
700 }
701 }
702}
703
704pub struct ContextMenuOptions {
705 pub min_entries_visible: usize,
706 pub max_entries_visible: usize,
707 pub placement: Option<ContextMenuPlacement>,
708}
709
710#[derive(Debug, Clone, PartialEq, Eq)]
711pub enum ContextMenuPlacement {
712 Above,
713 Below,
714}
715
716#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
717struct EditorActionId(usize);
718
719impl EditorActionId {
720 pub fn post_inc(&mut self) -> Self {
721 let answer = self.0;
722
723 *self = Self(answer + 1);
724
725 Self(answer)
726 }
727}
728
729// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
730// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
731
732type BackgroundHighlight = (
733 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
734 Arc<[Range<Anchor>]>,
735);
736type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
737
738#[derive(Default)]
739struct ScrollbarMarkerState {
740 scrollbar_size: Size<Pixels>,
741 dirty: bool,
742 markers: Arc<[PaintQuad]>,
743 pending_refresh: Option<Task<Result<()>>>,
744}
745
746impl ScrollbarMarkerState {
747 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
748 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
749 }
750}
751
752#[derive(Clone, Copy, PartialEq, Eq)]
753pub enum MinimapVisibility {
754 Disabled,
755 Enabled {
756 /// The configuration currently present in the users settings.
757 setting_configuration: bool,
758 /// Whether to override the currently set visibility from the users setting.
759 toggle_override: bool,
760 },
761}
762
763impl MinimapVisibility {
764 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
765 if mode.is_full() {
766 Self::Enabled {
767 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
768 toggle_override: false,
769 }
770 } else {
771 Self::Disabled
772 }
773 }
774
775 fn hidden(&self) -> Self {
776 match *self {
777 Self::Enabled {
778 setting_configuration,
779 ..
780 } => Self::Enabled {
781 setting_configuration,
782 toggle_override: setting_configuration,
783 },
784 Self::Disabled => Self::Disabled,
785 }
786 }
787
788 fn disabled(&self) -> bool {
789 matches!(*self, Self::Disabled)
790 }
791
792 fn settings_visibility(&self) -> bool {
793 match *self {
794 Self::Enabled {
795 setting_configuration,
796 ..
797 } => setting_configuration,
798 _ => false,
799 }
800 }
801
802 fn visible(&self) -> bool {
803 match *self {
804 Self::Enabled {
805 setting_configuration,
806 toggle_override,
807 } => setting_configuration ^ toggle_override,
808 _ => false,
809 }
810 }
811
812 fn toggle_visibility(&self) -> Self {
813 match *self {
814 Self::Enabled {
815 toggle_override,
816 setting_configuration,
817 } => Self::Enabled {
818 setting_configuration,
819 toggle_override: !toggle_override,
820 },
821 Self::Disabled => Self::Disabled,
822 }
823 }
824}
825
826#[derive(Debug, Clone, Copy, PartialEq, Eq)]
827pub enum BufferSerialization {
828 All,
829 NonDirtyBuffers,
830}
831
832impl BufferSerialization {
833 fn new(restore_unsaved_buffers: bool) -> Self {
834 if restore_unsaved_buffers {
835 Self::All
836 } else {
837 Self::NonDirtyBuffers
838 }
839 }
840}
841
842#[derive(Clone, Debug)]
843struct RunnableTasks {
844 templates: Vec<(TaskSourceKind, TaskTemplate)>,
845 offset: multi_buffer::Anchor,
846 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
847 column: u32,
848 // Values of all named captures, including those starting with '_'
849 extra_variables: HashMap<String, String>,
850 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
851 context_range: Range<BufferOffset>,
852}
853
854impl RunnableTasks {
855 fn resolve<'a>(
856 &'a self,
857 cx: &'a task::TaskContext,
858 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
859 self.templates.iter().filter_map(|(kind, template)| {
860 template
861 .resolve_task(&kind.to_id_base(), cx)
862 .map(|task| (kind.clone(), task))
863 })
864 }
865}
866
867#[derive(Clone)]
868pub struct ResolvedTasks {
869 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
870 position: Anchor,
871}
872
873/// Addons allow storing per-editor state in other crates (e.g. Vim)
874pub trait Addon: 'static {
875 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
876
877 fn render_buffer_header_controls(
878 &self,
879 _: &ExcerptInfo,
880 _: &Window,
881 _: &App,
882 ) -> Option<AnyElement> {
883 None
884 }
885
886 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
887 None
888 }
889
890 fn to_any(&self) -> &dyn std::any::Any;
891
892 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
893 None
894 }
895}
896
897struct ChangeLocation {
898 current: Option<Vec<Anchor>>,
899 original: Vec<Anchor>,
900}
901impl ChangeLocation {
902 fn locations(&self) -> &[Anchor] {
903 self.current.as_ref().unwrap_or(&self.original)
904 }
905}
906
907/// A set of caret positions, registered when the editor was edited.
908pub struct ChangeList {
909 changes: Vec<ChangeLocation>,
910 /// Currently "selected" change.
911 position: Option<usize>,
912}
913
914impl ChangeList {
915 pub fn new() -> Self {
916 Self {
917 changes: Vec::new(),
918 position: None,
919 }
920 }
921
922 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
923 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
924 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
925 if self.changes.is_empty() {
926 return None;
927 }
928
929 let prev = self.position.unwrap_or(self.changes.len());
930 let next = if direction == Direction::Prev {
931 prev.saturating_sub(count)
932 } else {
933 (prev + count).min(self.changes.len() - 1)
934 };
935 self.position = Some(next);
936 self.changes.get(next).map(|change| change.locations())
937 }
938
939 /// Adds a new change to the list, resetting the change list position.
940 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
941 self.position.take();
942 if let Some(last) = self.changes.last_mut()
943 && group
944 {
945 last.current = Some(new_positions)
946 } else {
947 self.changes.push(ChangeLocation {
948 original: new_positions,
949 current: None,
950 });
951 }
952 }
953
954 pub fn last(&self) -> Option<&[Anchor]> {
955 self.changes.last().map(|change| change.locations())
956 }
957
958 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
959 self.changes.last().map(|change| change.original.as_slice())
960 }
961
962 pub fn invert_last_group(&mut self) {
963 if let Some(last) = self.changes.last_mut()
964 && let Some(current) = last.current.as_mut()
965 {
966 mem::swap(&mut last.original, current);
967 }
968 }
969}
970
971#[derive(Clone)]
972struct InlineBlamePopoverState {
973 scroll_handle: ScrollHandle,
974 commit_message: Option<ParsedCommitMessage>,
975 markdown: Entity<Markdown>,
976}
977
978struct InlineBlamePopover {
979 position: gpui::Point<Pixels>,
980 hide_task: Option<Task<()>>,
981 popover_bounds: Option<Bounds<Pixels>>,
982 popover_state: InlineBlamePopoverState,
983 keyboard_grace: bool,
984}
985
986enum SelectionDragState {
987 /// State when no drag related activity is detected.
988 None,
989 /// State when the mouse is down on a selection that is about to be dragged.
990 ReadyToDrag {
991 selection: Selection<Anchor>,
992 click_position: gpui::Point<Pixels>,
993 mouse_down_time: Instant,
994 },
995 /// State when the mouse is dragging the selection in the editor.
996 Dragging {
997 selection: Selection<Anchor>,
998 drop_cursor: Selection<Anchor>,
999 hide_drop_cursor: bool,
1000 },
1001}
1002
1003enum ColumnarSelectionState {
1004 FromMouse {
1005 selection_tail: Anchor,
1006 display_point: Option<DisplayPoint>,
1007 },
1008 FromSelection {
1009 selection_tail: Anchor,
1010 },
1011}
1012
1013/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1014/// a breakpoint on them.
1015#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1016struct PhantomBreakpointIndicator {
1017 display_row: DisplayRow,
1018 /// There's a small debounce between hovering over the line and showing the indicator.
1019 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1020 is_active: bool,
1021 collides_with_existing_breakpoint: bool,
1022}
1023
1024/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1025/// in diff view mode.
1026#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1027pub(crate) struct PhantomDiffReviewIndicator {
1028 pub display_row: DisplayRow,
1029 /// There's a small debounce between hovering over the line and showing the indicator.
1030 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1031 pub is_active: bool,
1032}
1033
1034/// Identifies a specific hunk in the diff buffer.
1035/// Used as a key to group comments by their location.
1036#[derive(Clone, Debug)]
1037pub struct DiffHunkKey {
1038 /// The file path (relative to worktree) this hunk belongs to.
1039 pub file_path: Arc<util::rel_path::RelPath>,
1040 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1041 pub hunk_start_anchor: Anchor,
1042}
1043
1044/// A review comment stored locally before being sent to the Agent panel.
1045#[derive(Clone)]
1046pub struct StoredReviewComment {
1047 /// Unique identifier for this comment (for edit/delete operations).
1048 pub id: usize,
1049 /// The comment text entered by the user.
1050 pub comment: String,
1051 /// The display row where this comment was added (within the hunk).
1052 pub display_row: DisplayRow,
1053 /// Anchors for the code range being reviewed.
1054 pub anchor_range: Range<Anchor>,
1055 /// Timestamp when the comment was created (for chronological ordering).
1056 pub created_at: Instant,
1057 /// Whether this comment is currently being edited inline.
1058 pub is_editing: bool,
1059}
1060
1061impl StoredReviewComment {
1062 pub fn new(
1063 id: usize,
1064 comment: String,
1065 display_row: DisplayRow,
1066 anchor_range: Range<Anchor>,
1067 ) -> Self {
1068 Self {
1069 id,
1070 comment,
1071 display_row,
1072 anchor_range,
1073 created_at: Instant::now(),
1074 is_editing: false,
1075 }
1076 }
1077}
1078
1079/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1080pub(crate) struct DiffReviewOverlay {
1081 /// The display row where the overlay is anchored.
1082 pub display_row: DisplayRow,
1083 /// The block ID for the overlay.
1084 pub block_id: CustomBlockId,
1085 /// The editor entity for the review input.
1086 pub prompt_editor: Entity<Editor>,
1087 /// The hunk key this overlay belongs to.
1088 pub hunk_key: DiffHunkKey,
1089 /// Whether the comments section is expanded.
1090 pub comments_expanded: bool,
1091 /// Editors for comments currently being edited inline.
1092 /// Key: comment ID, Value: Editor entity for inline editing.
1093 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1094 /// Subscriptions for inline edit editors' action handlers.
1095 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1096 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1097 /// The current user's avatar URI for display in comment rows.
1098 pub user_avatar_uri: Option<SharedUri>,
1099 /// Subscription to keep the action handler alive.
1100 _subscription: Subscription,
1101}
1102
1103/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1104///
1105/// See the [module level documentation](self) for more information.
1106pub struct Editor {
1107 focus_handle: FocusHandle,
1108 last_focused_descendant: Option<WeakFocusHandle>,
1109 /// The text buffer being edited
1110 buffer: Entity<MultiBuffer>,
1111 /// Map of how text in the buffer should be displayed.
1112 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1113 pub display_map: Entity<DisplayMap>,
1114 placeholder_display_map: Option<Entity<DisplayMap>>,
1115 pub selections: SelectionsCollection,
1116 pub scroll_manager: ScrollManager,
1117 /// When inline assist editors are linked, they all render cursors because
1118 /// typing enters text into each of them, even the ones that aren't focused.
1119 pub(crate) show_cursor_when_unfocused: bool,
1120 columnar_selection_state: Option<ColumnarSelectionState>,
1121 add_selections_state: Option<AddSelectionsState>,
1122 select_next_state: Option<SelectNextState>,
1123 select_prev_state: Option<SelectNextState>,
1124 selection_history: SelectionHistory,
1125 defer_selection_effects: bool,
1126 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1127 autoclose_regions: Vec<AutocloseRegion>,
1128 snippet_stack: InvalidationStack<SnippetState>,
1129 select_syntax_node_history: SelectSyntaxNodeHistory,
1130 ime_transaction: Option<TransactionId>,
1131 pub diagnostics_max_severity: DiagnosticSeverity,
1132 active_diagnostics: ActiveDiagnostic,
1133 show_inline_diagnostics: bool,
1134 inline_diagnostics_update: Task<()>,
1135 inline_diagnostics_enabled: bool,
1136 diagnostics_enabled: bool,
1137 word_completions_enabled: bool,
1138 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1139 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1140 hard_wrap: Option<usize>,
1141 project: Option<Entity<Project>>,
1142 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1143 completion_provider: Option<Rc<dyn CompletionProvider>>,
1144 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1145 blink_manager: Entity<BlinkManager>,
1146 show_cursor_names: bool,
1147 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1148 pub show_local_selections: bool,
1149 mode: EditorMode,
1150 show_breadcrumbs: bool,
1151 show_gutter: bool,
1152 show_scrollbars: ScrollbarAxes,
1153 minimap_visibility: MinimapVisibility,
1154 offset_content: bool,
1155 disable_expand_excerpt_buttons: bool,
1156 delegate_expand_excerpts: bool,
1157 show_line_numbers: Option<bool>,
1158 use_relative_line_numbers: Option<bool>,
1159 show_git_diff_gutter: Option<bool>,
1160 show_code_actions: Option<bool>,
1161 show_runnables: Option<bool>,
1162 show_breakpoints: Option<bool>,
1163 show_diff_review_button: bool,
1164 show_wrap_guides: Option<bool>,
1165 show_indent_guides: Option<bool>,
1166 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1167 highlight_order: usize,
1168 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1169 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1170 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1171 scrollbar_marker_state: ScrollbarMarkerState,
1172 active_indent_guides_state: ActiveIndentGuidesState,
1173 nav_history: Option<ItemNavHistory>,
1174 context_menu: RefCell<Option<CodeContextMenu>>,
1175 context_menu_options: Option<ContextMenuOptions>,
1176 mouse_context_menu: Option<MouseContextMenu>,
1177 completion_tasks: Vec<(CompletionId, Task<()>)>,
1178 inline_blame_popover: Option<InlineBlamePopover>,
1179 inline_blame_popover_show_task: Option<Task<()>>,
1180 signature_help_state: SignatureHelpState,
1181 auto_signature_help: Option<bool>,
1182 find_all_references_task_sources: Vec<Anchor>,
1183 next_completion_id: CompletionId,
1184 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1185 code_actions_task: Option<Task<Result<()>>>,
1186 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1187 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1188 debounced_selection_highlight_complete: bool,
1189 document_highlights_task: Option<Task<()>>,
1190 linked_editing_range_task: Option<Task<Option<()>>>,
1191 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1192 pending_rename: Option<RenameState>,
1193 searchable: bool,
1194 cursor_shape: CursorShape,
1195 /// Whether the cursor is offset one character to the left when something is
1196 /// selected (needed for vim visual mode)
1197 cursor_offset_on_selection: bool,
1198 current_line_highlight: Option<CurrentLineHighlight>,
1199 pub collapse_matches: bool,
1200 autoindent_mode: Option<AutoindentMode>,
1201 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1202 input_enabled: bool,
1203 use_modal_editing: bool,
1204 read_only: bool,
1205 leader_id: Option<CollaboratorId>,
1206 remote_id: Option<ViewId>,
1207 pub hover_state: HoverState,
1208 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1209 prev_pressure_stage: Option<PressureStage>,
1210 gutter_hovered: bool,
1211 hovered_link_state: Option<HoveredLinkState>,
1212 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1213 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1214 active_edit_prediction: Option<EditPredictionState>,
1215 /// Used to prevent flickering as the user types while the menu is open
1216 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1217 edit_prediction_settings: EditPredictionSettings,
1218 edit_predictions_hidden_for_vim_mode: bool,
1219 show_edit_predictions_override: Option<bool>,
1220 show_completions_on_input_override: Option<bool>,
1221 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1222 edit_prediction_preview: EditPredictionPreview,
1223 edit_prediction_indent_conflict: bool,
1224 edit_prediction_requires_modifier_in_indent_conflict: bool,
1225 next_inlay_id: usize,
1226 next_color_inlay_id: usize,
1227 _subscriptions: Vec<Subscription>,
1228 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1229 gutter_dimensions: GutterDimensions,
1230 style: Option<EditorStyle>,
1231 text_style_refinement: Option<TextStyleRefinement>,
1232 next_editor_action_id: EditorActionId,
1233 editor_actions: Rc<
1234 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1235 >,
1236 use_autoclose: bool,
1237 use_auto_surround: bool,
1238 auto_replace_emoji_shortcode: bool,
1239 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1240 show_git_blame_gutter: bool,
1241 show_git_blame_inline: bool,
1242 show_git_blame_inline_delay_task: Option<Task<()>>,
1243 git_blame_inline_enabled: bool,
1244 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1245 buffer_serialization: Option<BufferSerialization>,
1246 show_selection_menu: Option<bool>,
1247 blame: Option<Entity<GitBlame>>,
1248 blame_subscription: Option<Subscription>,
1249 custom_context_menu: Option<
1250 Box<
1251 dyn 'static
1252 + Fn(
1253 &mut Self,
1254 DisplayPoint,
1255 &mut Window,
1256 &mut Context<Self>,
1257 ) -> Option<Entity<ui::ContextMenu>>,
1258 >,
1259 >,
1260 last_bounds: Option<Bounds<Pixels>>,
1261 last_position_map: Option<Rc<PositionMap>>,
1262 expect_bounds_change: Option<Bounds<Pixels>>,
1263 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1264 tasks_update_task: Option<Task<()>>,
1265 breakpoint_store: Option<Entity<BreakpointStore>>,
1266 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1267 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1268 /// Active diff review overlays. Multiple overlays can be open simultaneously
1269 /// when hunks have comments stored.
1270 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1271 /// Stored review comments grouped by hunk.
1272 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1273 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1274 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1275 /// Counter for generating unique comment IDs.
1276 next_review_comment_id: usize,
1277 hovered_diff_hunk_row: Option<DisplayRow>,
1278 pull_diagnostics_task: Task<()>,
1279 pull_diagnostics_background_task: Task<()>,
1280 in_project_search: bool,
1281 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1282 breadcrumb_header: Option<String>,
1283 focused_block: Option<FocusedBlock>,
1284 next_scroll_position: NextScrollCursorCenterTopBottom,
1285 addons: HashMap<TypeId, Box<dyn Addon>>,
1286 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1287 load_diff_task: Option<Shared<Task<()>>>,
1288 /// Whether we are temporarily displaying a diff other than git's
1289 temporary_diff_override: bool,
1290 selection_mark_mode: bool,
1291 toggle_fold_multiple_buffers: Task<()>,
1292 _scroll_cursor_center_top_bottom_task: Task<()>,
1293 serialize_selections: Task<()>,
1294 serialize_folds: Task<()>,
1295 mouse_cursor_hidden: bool,
1296 minimap: Option<Entity<Self>>,
1297 hide_mouse_mode: HideMouseMode,
1298 pub change_list: ChangeList,
1299 inline_value_cache: InlineValueCache,
1300 number_deleted_lines: bool,
1301
1302 selection_drag_state: SelectionDragState,
1303 colors: Option<LspColorData>,
1304 post_scroll_update: Task<()>,
1305 refresh_colors_task: Task<()>,
1306 inlay_hints: Option<LspInlayHintData>,
1307 folding_newlines: Task<()>,
1308 select_next_is_case_sensitive: Option<bool>,
1309 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1310 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1311 accent_data: Option<AccentData>,
1312 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1313}
1314
1315#[derive(Debug, PartialEq)]
1316struct AccentData {
1317 colors: AccentColors,
1318 overrides: Vec<SharedString>,
1319}
1320
1321fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1322 if debounce_ms > 0 {
1323 Some(Duration::from_millis(debounce_ms))
1324 } else {
1325 None
1326 }
1327}
1328
1329#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1330enum NextScrollCursorCenterTopBottom {
1331 #[default]
1332 Center,
1333 Top,
1334 Bottom,
1335}
1336
1337impl NextScrollCursorCenterTopBottom {
1338 fn next(&self) -> Self {
1339 match self {
1340 Self::Center => Self::Top,
1341 Self::Top => Self::Bottom,
1342 Self::Bottom => Self::Center,
1343 }
1344 }
1345}
1346
1347#[derive(Clone)]
1348pub struct EditorSnapshot {
1349 pub mode: EditorMode,
1350 show_gutter: bool,
1351 offset_content: bool,
1352 show_line_numbers: Option<bool>,
1353 number_deleted_lines: bool,
1354 show_git_diff_gutter: Option<bool>,
1355 show_code_actions: Option<bool>,
1356 show_runnables: Option<bool>,
1357 show_breakpoints: Option<bool>,
1358 git_blame_gutter_max_author_length: Option<usize>,
1359 pub display_snapshot: DisplaySnapshot,
1360 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1361 is_focused: bool,
1362 scroll_anchor: ScrollAnchor,
1363 ongoing_scroll: OngoingScroll,
1364 current_line_highlight: CurrentLineHighlight,
1365 gutter_hovered: bool,
1366}
1367
1368#[derive(Default, Debug, Clone, Copy)]
1369pub struct GutterDimensions {
1370 pub left_padding: Pixels,
1371 pub right_padding: Pixels,
1372 pub width: Pixels,
1373 pub margin: Pixels,
1374 pub git_blame_entries_width: Option<Pixels>,
1375}
1376
1377impl GutterDimensions {
1378 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1379 Self {
1380 margin: Self::default_gutter_margin(font_id, font_size, cx),
1381 ..Default::default()
1382 }
1383 }
1384
1385 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1386 -cx.text_system().descent(font_id, font_size)
1387 }
1388 /// The full width of the space taken up by the gutter.
1389 pub fn full_width(&self) -> Pixels {
1390 self.margin + self.width
1391 }
1392
1393 /// The width of the space reserved for the fold indicators,
1394 /// use alongside 'justify_end' and `gutter_width` to
1395 /// right align content with the line numbers
1396 pub fn fold_area_width(&self) -> Pixels {
1397 self.margin + self.right_padding
1398 }
1399}
1400
1401struct CharacterDimensions {
1402 em_width: Pixels,
1403 em_advance: Pixels,
1404 line_height: Pixels,
1405}
1406
1407#[derive(Debug)]
1408pub struct RemoteSelection {
1409 pub replica_id: ReplicaId,
1410 pub selection: Selection<Anchor>,
1411 pub cursor_shape: CursorShape,
1412 pub collaborator_id: CollaboratorId,
1413 pub line_mode: bool,
1414 pub user_name: Option<SharedString>,
1415 pub color: PlayerColor,
1416}
1417
1418#[derive(Clone, Debug)]
1419struct SelectionHistoryEntry {
1420 selections: Arc<[Selection<Anchor>]>,
1421 select_next_state: Option<SelectNextState>,
1422 select_prev_state: Option<SelectNextState>,
1423 add_selections_state: Option<AddSelectionsState>,
1424}
1425
1426#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1427enum SelectionHistoryMode {
1428 #[default]
1429 Normal,
1430 Undoing,
1431 Redoing,
1432 Skipping,
1433}
1434
1435#[derive(Clone, PartialEq, Eq, Hash)]
1436struct HoveredCursor {
1437 replica_id: ReplicaId,
1438 selection_id: usize,
1439}
1440
1441#[derive(Debug)]
1442/// SelectionEffects controls the side-effects of updating the selection.
1443///
1444/// The default behaviour does "what you mostly want":
1445/// - it pushes to the nav history if the cursor moved by >10 lines
1446/// - it re-triggers completion requests
1447/// - it scrolls to fit
1448///
1449/// You might want to modify these behaviours. For example when doing a "jump"
1450/// like go to definition, we always want to add to nav history; but when scrolling
1451/// in vim mode we never do.
1452///
1453/// Similarly, you might want to disable scrolling if you don't want the viewport to
1454/// move.
1455#[derive(Clone)]
1456pub struct SelectionEffects {
1457 nav_history: Option<bool>,
1458 completions: bool,
1459 scroll: Option<Autoscroll>,
1460}
1461
1462impl Default for SelectionEffects {
1463 fn default() -> Self {
1464 Self {
1465 nav_history: None,
1466 completions: true,
1467 scroll: Some(Autoscroll::fit()),
1468 }
1469 }
1470}
1471impl SelectionEffects {
1472 pub fn scroll(scroll: Autoscroll) -> Self {
1473 Self {
1474 scroll: Some(scroll),
1475 ..Default::default()
1476 }
1477 }
1478
1479 pub fn no_scroll() -> Self {
1480 Self {
1481 scroll: None,
1482 ..Default::default()
1483 }
1484 }
1485
1486 pub fn completions(self, completions: bool) -> Self {
1487 Self {
1488 completions,
1489 ..self
1490 }
1491 }
1492
1493 pub fn nav_history(self, nav_history: bool) -> Self {
1494 Self {
1495 nav_history: Some(nav_history),
1496 ..self
1497 }
1498 }
1499}
1500
1501struct DeferredSelectionEffectsState {
1502 changed: bool,
1503 effects: SelectionEffects,
1504 old_cursor_position: Anchor,
1505 history_entry: SelectionHistoryEntry,
1506}
1507
1508#[derive(Default)]
1509struct SelectionHistory {
1510 #[allow(clippy::type_complexity)]
1511 selections_by_transaction:
1512 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1513 mode: SelectionHistoryMode,
1514 undo_stack: VecDeque<SelectionHistoryEntry>,
1515 redo_stack: VecDeque<SelectionHistoryEntry>,
1516}
1517
1518impl SelectionHistory {
1519 #[track_caller]
1520 fn insert_transaction(
1521 &mut self,
1522 transaction_id: TransactionId,
1523 selections: Arc<[Selection<Anchor>]>,
1524 ) {
1525 if selections.is_empty() {
1526 log::error!(
1527 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1528 std::panic::Location::caller()
1529 );
1530 return;
1531 }
1532 self.selections_by_transaction
1533 .insert(transaction_id, (selections, None));
1534 }
1535
1536 #[allow(clippy::type_complexity)]
1537 fn transaction(
1538 &self,
1539 transaction_id: TransactionId,
1540 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1541 self.selections_by_transaction.get(&transaction_id)
1542 }
1543
1544 #[allow(clippy::type_complexity)]
1545 fn transaction_mut(
1546 &mut self,
1547 transaction_id: TransactionId,
1548 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1549 self.selections_by_transaction.get_mut(&transaction_id)
1550 }
1551
1552 fn push(&mut self, entry: SelectionHistoryEntry) {
1553 if !entry.selections.is_empty() {
1554 match self.mode {
1555 SelectionHistoryMode::Normal => {
1556 self.push_undo(entry);
1557 self.redo_stack.clear();
1558 }
1559 SelectionHistoryMode::Undoing => self.push_redo(entry),
1560 SelectionHistoryMode::Redoing => self.push_undo(entry),
1561 SelectionHistoryMode::Skipping => {}
1562 }
1563 }
1564 }
1565
1566 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1567 if self
1568 .undo_stack
1569 .back()
1570 .is_none_or(|e| e.selections != entry.selections)
1571 {
1572 self.undo_stack.push_back(entry);
1573 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1574 self.undo_stack.pop_front();
1575 }
1576 }
1577 }
1578
1579 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1580 if self
1581 .redo_stack
1582 .back()
1583 .is_none_or(|e| e.selections != entry.selections)
1584 {
1585 self.redo_stack.push_back(entry);
1586 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1587 self.redo_stack.pop_front();
1588 }
1589 }
1590 }
1591}
1592
1593#[derive(Clone, Copy)]
1594pub struct RowHighlightOptions {
1595 pub autoscroll: bool,
1596 pub include_gutter: bool,
1597}
1598
1599impl Default for RowHighlightOptions {
1600 fn default() -> Self {
1601 Self {
1602 autoscroll: Default::default(),
1603 include_gutter: true,
1604 }
1605 }
1606}
1607
1608struct RowHighlight {
1609 index: usize,
1610 range: Range<Anchor>,
1611 color: Hsla,
1612 options: RowHighlightOptions,
1613 type_id: TypeId,
1614}
1615
1616#[derive(Clone, Debug)]
1617struct AddSelectionsState {
1618 groups: Vec<AddSelectionsGroup>,
1619}
1620
1621#[derive(Clone, Debug)]
1622struct AddSelectionsGroup {
1623 above: bool,
1624 stack: Vec<usize>,
1625}
1626
1627#[derive(Clone)]
1628struct SelectNextState {
1629 query: AhoCorasick,
1630 wordwise: bool,
1631 done: bool,
1632}
1633
1634impl std::fmt::Debug for SelectNextState {
1635 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1636 f.debug_struct(std::any::type_name::<Self>())
1637 .field("wordwise", &self.wordwise)
1638 .field("done", &self.done)
1639 .finish()
1640 }
1641}
1642
1643#[derive(Debug)]
1644struct AutocloseRegion {
1645 selection_id: usize,
1646 range: Range<Anchor>,
1647 pair: BracketPair,
1648}
1649
1650#[derive(Debug)]
1651struct SnippetState {
1652 ranges: Vec<Vec<Range<Anchor>>>,
1653 active_index: usize,
1654 choices: Vec<Option<Vec<String>>>,
1655}
1656
1657#[doc(hidden)]
1658pub struct RenameState {
1659 pub range: Range<Anchor>,
1660 pub old_name: Arc<str>,
1661 pub editor: Entity<Editor>,
1662 block_id: CustomBlockId,
1663}
1664
1665struct InvalidationStack<T>(Vec<T>);
1666
1667struct RegisteredEditPredictionDelegate {
1668 provider: Arc<dyn EditPredictionDelegateHandle>,
1669 _subscription: Subscription,
1670}
1671
1672#[derive(Debug, PartialEq, Eq)]
1673pub struct ActiveDiagnosticGroup {
1674 pub active_range: Range<Anchor>,
1675 pub active_message: String,
1676 pub group_id: usize,
1677 pub blocks: HashSet<CustomBlockId>,
1678}
1679
1680#[derive(Debug, PartialEq, Eq)]
1681
1682pub(crate) enum ActiveDiagnostic {
1683 None,
1684 All,
1685 Group(ActiveDiagnosticGroup),
1686}
1687
1688#[derive(Serialize, Deserialize, Clone, Debug)]
1689pub struct ClipboardSelection {
1690 /// The number of bytes in this selection.
1691 pub len: usize,
1692 /// Whether this was a full-line selection.
1693 pub is_entire_line: bool,
1694 /// The indentation of the first line when this content was originally copied.
1695 pub first_line_indent: u32,
1696 #[serde(default)]
1697 pub file_path: Option<PathBuf>,
1698 #[serde(default)]
1699 pub line_range: Option<RangeInclusive<u32>>,
1700}
1701
1702impl ClipboardSelection {
1703 pub fn for_buffer(
1704 len: usize,
1705 is_entire_line: bool,
1706 range: Range<Point>,
1707 buffer: &MultiBufferSnapshot,
1708 project: Option<&Entity<Project>>,
1709 cx: &App,
1710 ) -> Self {
1711 let first_line_indent = buffer
1712 .indent_size_for_line(MultiBufferRow(range.start.row))
1713 .len;
1714
1715 let file_path = util::maybe!({
1716 let project = project?.read(cx);
1717 let file = buffer.file_at(range.start)?;
1718 let project_path = ProjectPath {
1719 worktree_id: file.worktree_id(cx),
1720 path: file.path().clone(),
1721 };
1722 project.absolute_path(&project_path, cx)
1723 });
1724
1725 let line_range = file_path.as_ref().and_then(|_| {
1726 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1727 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1728 if start_excerpt_id == end_excerpt_id {
1729 Some(start_point.row..=end_point.row)
1730 } else {
1731 None
1732 }
1733 });
1734
1735 Self {
1736 len,
1737 is_entire_line,
1738 first_line_indent,
1739 file_path,
1740 line_range,
1741 }
1742 }
1743}
1744
1745// selections, scroll behavior, was newest selection reversed
1746type SelectSyntaxNodeHistoryState = (
1747 Box<[Selection<MultiBufferOffset>]>,
1748 SelectSyntaxNodeScrollBehavior,
1749 bool,
1750);
1751
1752#[derive(Default)]
1753struct SelectSyntaxNodeHistory {
1754 stack: Vec<SelectSyntaxNodeHistoryState>,
1755 // disable temporarily to allow changing selections without losing the stack
1756 pub disable_clearing: bool,
1757}
1758
1759impl SelectSyntaxNodeHistory {
1760 pub fn try_clear(&mut self) {
1761 if !self.disable_clearing {
1762 self.stack.clear();
1763 }
1764 }
1765
1766 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1767 self.stack.push(selection);
1768 }
1769
1770 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1771 self.stack.pop()
1772 }
1773}
1774
1775enum SelectSyntaxNodeScrollBehavior {
1776 CursorTop,
1777 FitSelection,
1778 CursorBottom,
1779}
1780
1781#[derive(Debug, Clone, Copy)]
1782pub(crate) struct NavigationData {
1783 cursor_anchor: Anchor,
1784 cursor_position: Point,
1785 scroll_anchor: ScrollAnchor,
1786 scroll_top_row: u32,
1787}
1788
1789#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1790pub enum GotoDefinitionKind {
1791 Symbol,
1792 Declaration,
1793 Type,
1794 Implementation,
1795}
1796
1797pub enum FormatTarget {
1798 Buffers(HashSet<Entity<Buffer>>),
1799 Ranges(Vec<Range<MultiBufferPoint>>),
1800}
1801
1802pub(crate) struct FocusedBlock {
1803 id: BlockId,
1804 focus_handle: WeakFocusHandle,
1805}
1806
1807#[derive(Clone, Debug)]
1808enum JumpData {
1809 MultiBufferRow {
1810 row: MultiBufferRow,
1811 line_offset_from_top: u32,
1812 },
1813 MultiBufferPoint {
1814 excerpt_id: ExcerptId,
1815 position: Point,
1816 anchor: text::Anchor,
1817 line_offset_from_top: u32,
1818 },
1819}
1820
1821pub enum MultibufferSelectionMode {
1822 First,
1823 All,
1824}
1825
1826#[derive(Clone, Copy, Debug, Default)]
1827pub struct RewrapOptions {
1828 pub override_language_settings: bool,
1829 pub preserve_existing_whitespace: bool,
1830}
1831
1832impl Editor {
1833 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1834 let buffer = cx.new(|cx| Buffer::local("", cx));
1835 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1836 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1837 }
1838
1839 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1840 let buffer = cx.new(|cx| Buffer::local("", cx));
1841 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1842 Self::new(EditorMode::full(), buffer, None, window, cx)
1843 }
1844
1845 pub fn auto_height(
1846 min_lines: usize,
1847 max_lines: usize,
1848 window: &mut Window,
1849 cx: &mut Context<Self>,
1850 ) -> Self {
1851 let buffer = cx.new(|cx| Buffer::local("", cx));
1852 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1853 Self::new(
1854 EditorMode::AutoHeight {
1855 min_lines,
1856 max_lines: Some(max_lines),
1857 },
1858 buffer,
1859 None,
1860 window,
1861 cx,
1862 )
1863 }
1864
1865 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1866 /// The editor grows as tall as needed to fit its content.
1867 pub fn auto_height_unbounded(
1868 min_lines: usize,
1869 window: &mut Window,
1870 cx: &mut Context<Self>,
1871 ) -> Self {
1872 let buffer = cx.new(|cx| Buffer::local("", cx));
1873 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1874 Self::new(
1875 EditorMode::AutoHeight {
1876 min_lines,
1877 max_lines: None,
1878 },
1879 buffer,
1880 None,
1881 window,
1882 cx,
1883 )
1884 }
1885
1886 pub fn for_buffer(
1887 buffer: Entity<Buffer>,
1888 project: Option<Entity<Project>>,
1889 window: &mut Window,
1890 cx: &mut Context<Self>,
1891 ) -> Self {
1892 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1893 Self::new(EditorMode::full(), buffer, project, window, cx)
1894 }
1895
1896 pub fn for_multibuffer(
1897 buffer: Entity<MultiBuffer>,
1898 project: Option<Entity<Project>>,
1899 window: &mut Window,
1900 cx: &mut Context<Self>,
1901 ) -> Self {
1902 Self::new(EditorMode::full(), buffer, project, window, cx)
1903 }
1904
1905 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1906 let mut clone = Self::new(
1907 self.mode.clone(),
1908 self.buffer.clone(),
1909 self.project.clone(),
1910 window,
1911 cx,
1912 );
1913 self.display_map.update(cx, |display_map, cx| {
1914 let snapshot = display_map.snapshot(cx);
1915 clone.display_map.update(cx, |display_map, cx| {
1916 display_map.set_state(&snapshot, cx);
1917 });
1918 });
1919 clone.folds_did_change(cx);
1920 clone.selections.clone_state(&self.selections);
1921 clone.scroll_manager.clone_state(&self.scroll_manager);
1922 clone.searchable = self.searchable;
1923 clone.read_only = self.read_only;
1924 clone
1925 }
1926
1927 pub fn new(
1928 mode: EditorMode,
1929 buffer: Entity<MultiBuffer>,
1930 project: Option<Entity<Project>>,
1931 window: &mut Window,
1932 cx: &mut Context<Self>,
1933 ) -> Self {
1934 Editor::new_internal(mode, buffer, project, None, window, cx)
1935 }
1936
1937 pub fn sticky_headers(
1938 &self,
1939 style: &EditorStyle,
1940 cx: &App,
1941 ) -> Option<Vec<OutlineItem<Anchor>>> {
1942 let multi_buffer = self.buffer().read(cx);
1943 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1944 let multi_buffer_visible_start = self
1945 .scroll_manager
1946 .anchor()
1947 .anchor
1948 .to_point(&multi_buffer_snapshot);
1949 let max_row = multi_buffer_snapshot.max_point().row;
1950
1951 let start_row = (multi_buffer_visible_start.row).min(max_row);
1952 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1953
1954 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1955 let outline_items = buffer
1956 .outline_items_containing(
1957 Point::new(start_row, 0)..Point::new(end_row, 0),
1958 true,
1959 Some(style.syntax.as_ref()),
1960 )
1961 .into_iter()
1962 .map(|outline_item| OutlineItem {
1963 depth: outline_item.depth,
1964 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1965 source_range_for_text: Anchor::range_in_buffer(
1966 *excerpt_id,
1967 outline_item.source_range_for_text,
1968 ),
1969 text: outline_item.text,
1970 highlight_ranges: outline_item.highlight_ranges,
1971 name_ranges: outline_item.name_ranges,
1972 body_range: outline_item
1973 .body_range
1974 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1975 annotation_range: outline_item
1976 .annotation_range
1977 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1978 });
1979 return Some(outline_items.collect());
1980 }
1981
1982 None
1983 }
1984
1985 fn new_internal(
1986 mode: EditorMode,
1987 multi_buffer: Entity<MultiBuffer>,
1988 project: Option<Entity<Project>>,
1989 display_map: Option<Entity<DisplayMap>>,
1990 window: &mut Window,
1991 cx: &mut Context<Self>,
1992 ) -> Self {
1993 debug_assert!(
1994 display_map.is_none() || mode.is_minimap(),
1995 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1996 );
1997
1998 let full_mode = mode.is_full();
1999 let is_minimap = mode.is_minimap();
2000 let diagnostics_max_severity = if full_mode {
2001 EditorSettings::get_global(cx)
2002 .diagnostics_max_severity
2003 .unwrap_or(DiagnosticSeverity::Hint)
2004 } else {
2005 DiagnosticSeverity::Off
2006 };
2007 let style = window.text_style();
2008 let font_size = style.font_size.to_pixels(window.rem_size());
2009 let editor = cx.entity().downgrade();
2010 let fold_placeholder = FoldPlaceholder {
2011 constrain_width: false,
2012 render: Arc::new(move |fold_id, fold_range, cx| {
2013 let editor = editor.clone();
2014 div()
2015 .id(fold_id)
2016 .bg(cx.theme().colors().ghost_element_background)
2017 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
2018 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
2019 .rounded_xs()
2020 .size_full()
2021 .cursor_pointer()
2022 .child("⋯")
2023 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2024 .on_click(move |_, _window, cx| {
2025 editor
2026 .update(cx, |editor, cx| {
2027 editor.unfold_ranges(
2028 &[fold_range.start..fold_range.end],
2029 true,
2030 false,
2031 cx,
2032 );
2033 cx.stop_propagation();
2034 })
2035 .ok();
2036 })
2037 .into_any()
2038 }),
2039 merge_adjacent: true,
2040 ..FoldPlaceholder::default()
2041 };
2042 let display_map = display_map.unwrap_or_else(|| {
2043 cx.new(|cx| {
2044 DisplayMap::new(
2045 multi_buffer.clone(),
2046 style.font(),
2047 font_size,
2048 None,
2049 FILE_HEADER_HEIGHT,
2050 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2051 fold_placeholder,
2052 diagnostics_max_severity,
2053 cx,
2054 )
2055 })
2056 });
2057
2058 let selections = SelectionsCollection::new();
2059
2060 let blink_manager = cx.new(|cx| {
2061 let mut blink_manager = BlinkManager::new(
2062 CURSOR_BLINK_INTERVAL,
2063 |cx| EditorSettings::get_global(cx).cursor_blink,
2064 cx,
2065 );
2066 if is_minimap {
2067 blink_manager.disable(cx);
2068 }
2069 blink_manager
2070 });
2071
2072 let soft_wrap_mode_override =
2073 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2074
2075 let mut project_subscriptions = Vec::new();
2076 if full_mode && let Some(project) = project.as_ref() {
2077 project_subscriptions.push(cx.subscribe_in(
2078 project,
2079 window,
2080 |editor, _, event, window, cx| match event {
2081 project::Event::RefreshCodeLens => {
2082 // we always query lens with actions, without storing them, always refreshing them
2083 }
2084 project::Event::RefreshInlayHints {
2085 server_id,
2086 request_id,
2087 } => {
2088 editor.refresh_inlay_hints(
2089 InlayHintRefreshReason::RefreshRequested {
2090 server_id: *server_id,
2091 request_id: *request_id,
2092 },
2093 cx,
2094 );
2095 }
2096 project::Event::LanguageServerRemoved(..) => {
2097 if editor.tasks_update_task.is_none() {
2098 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2099 }
2100 editor.registered_buffers.clear();
2101 editor.register_visible_buffers(cx);
2102 }
2103 project::Event::LanguageServerAdded(..) => {
2104 if editor.tasks_update_task.is_none() {
2105 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2106 }
2107 }
2108 project::Event::SnippetEdit(id, snippet_edits) => {
2109 // todo(lw): Non singletons
2110 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2111 let snapshot = buffer.read(cx).snapshot();
2112 let focus_handle = editor.focus_handle(cx);
2113 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2114 for (range, snippet) in snippet_edits {
2115 let buffer_range =
2116 language::range_from_lsp(*range).to_offset(&snapshot);
2117 editor
2118 .insert_snippet(
2119 &[MultiBufferOffset(buffer_range.start)
2120 ..MultiBufferOffset(buffer_range.end)],
2121 snippet.clone(),
2122 window,
2123 cx,
2124 )
2125 .ok();
2126 }
2127 }
2128 }
2129 }
2130 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2131 let buffer_id = *buffer_id;
2132 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2133 editor.register_buffer(buffer_id, cx);
2134 editor.update_lsp_data(Some(buffer_id), window, cx);
2135 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2136 refresh_linked_ranges(editor, window, cx);
2137 editor.refresh_code_actions(window, cx);
2138 editor.refresh_document_highlights(cx);
2139 }
2140 }
2141
2142 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2143 let Some(workspace) = editor.workspace() else {
2144 return;
2145 };
2146 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2147 else {
2148 return;
2149 };
2150
2151 if active_editor.entity_id() == cx.entity_id() {
2152 let entity_id = cx.entity_id();
2153 workspace.update(cx, |this, cx| {
2154 this.panes_mut()
2155 .iter_mut()
2156 .filter(|pane| pane.entity_id() != entity_id)
2157 .for_each(|p| {
2158 p.update(cx, |pane, _| {
2159 pane.nav_history_mut().rename_item(
2160 entity_id,
2161 project_path.clone(),
2162 abs_path.clone().into(),
2163 );
2164 })
2165 });
2166 });
2167
2168 Self::open_transaction_for_hidden_buffers(
2169 workspace,
2170 transaction.clone(),
2171 "Rename".to_string(),
2172 window,
2173 cx,
2174 );
2175 }
2176 }
2177
2178 project::Event::WorkspaceEditApplied(transaction) => {
2179 let Some(workspace) = editor.workspace() else {
2180 return;
2181 };
2182 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2183 else {
2184 return;
2185 };
2186
2187 if active_editor.entity_id() == cx.entity_id() {
2188 Self::open_transaction_for_hidden_buffers(
2189 workspace,
2190 transaction.clone(),
2191 "LSP Edit".to_string(),
2192 window,
2193 cx,
2194 );
2195 }
2196 }
2197
2198 _ => {}
2199 },
2200 ));
2201 if let Some(task_inventory) = project
2202 .read(cx)
2203 .task_store()
2204 .read(cx)
2205 .task_inventory()
2206 .cloned()
2207 {
2208 project_subscriptions.push(cx.observe_in(
2209 &task_inventory,
2210 window,
2211 |editor, _, window, cx| {
2212 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2213 },
2214 ));
2215 };
2216
2217 project_subscriptions.push(cx.subscribe_in(
2218 &project.read(cx).breakpoint_store(),
2219 window,
2220 |editor, _, event, window, cx| match event {
2221 BreakpointStoreEvent::ClearDebugLines => {
2222 editor.clear_row_highlights::<ActiveDebugLine>();
2223 editor.refresh_inline_values(cx);
2224 }
2225 BreakpointStoreEvent::SetDebugLine => {
2226 if editor.go_to_active_debug_line(window, cx) {
2227 cx.stop_propagation();
2228 }
2229
2230 editor.refresh_inline_values(cx);
2231 }
2232 _ => {}
2233 },
2234 ));
2235 let git_store = project.read(cx).git_store().clone();
2236 let project = project.clone();
2237 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2238 if let GitStoreEvent::RepositoryAdded = event {
2239 this.load_diff_task = Some(
2240 update_uncommitted_diff_for_buffer(
2241 cx.entity(),
2242 &project,
2243 this.buffer.read(cx).all_buffers(),
2244 this.buffer.clone(),
2245 cx,
2246 )
2247 .shared(),
2248 );
2249 }
2250 }));
2251 }
2252
2253 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2254
2255 let inlay_hint_settings =
2256 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2257 let focus_handle = cx.focus_handle();
2258 if !is_minimap {
2259 cx.on_focus(&focus_handle, window, Self::handle_focus)
2260 .detach();
2261 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2262 .detach();
2263 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2264 .detach();
2265 cx.on_blur(&focus_handle, window, Self::handle_blur)
2266 .detach();
2267 cx.observe_pending_input(window, Self::observe_pending_input)
2268 .detach();
2269 }
2270
2271 let show_indent_guides =
2272 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2273 Some(false)
2274 } else {
2275 None
2276 };
2277
2278 let breakpoint_store = match (&mode, project.as_ref()) {
2279 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2280 _ => None,
2281 };
2282
2283 let mut code_action_providers = Vec::new();
2284 let mut load_uncommitted_diff = None;
2285 if let Some(project) = project.clone() {
2286 load_uncommitted_diff = Some(
2287 update_uncommitted_diff_for_buffer(
2288 cx.entity(),
2289 &project,
2290 multi_buffer.read(cx).all_buffers(),
2291 multi_buffer.clone(),
2292 cx,
2293 )
2294 .shared(),
2295 );
2296 code_action_providers.push(Rc::new(project) as Rc<_>);
2297 }
2298
2299 let mut editor = Self {
2300 focus_handle,
2301 show_cursor_when_unfocused: false,
2302 last_focused_descendant: None,
2303 buffer: multi_buffer.clone(),
2304 display_map: display_map.clone(),
2305 placeholder_display_map: None,
2306 selections,
2307 scroll_manager: ScrollManager::new(cx),
2308 columnar_selection_state: None,
2309 add_selections_state: None,
2310 select_next_state: None,
2311 select_prev_state: None,
2312 selection_history: SelectionHistory::default(),
2313 defer_selection_effects: false,
2314 deferred_selection_effects_state: None,
2315 autoclose_regions: Vec::new(),
2316 snippet_stack: InvalidationStack::default(),
2317 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2318 ime_transaction: None,
2319 active_diagnostics: ActiveDiagnostic::None,
2320 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2321 inline_diagnostics_update: Task::ready(()),
2322 inline_diagnostics: Vec::new(),
2323 soft_wrap_mode_override,
2324 diagnostics_max_severity,
2325 hard_wrap: None,
2326 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2327 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2328 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2329 project,
2330 blink_manager: blink_manager.clone(),
2331 show_local_selections: true,
2332 show_scrollbars: ScrollbarAxes {
2333 horizontal: full_mode,
2334 vertical: full_mode,
2335 },
2336 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2337 offset_content: !matches!(mode, EditorMode::SingleLine),
2338 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2339 show_gutter: full_mode,
2340 show_line_numbers: (!full_mode).then_some(false),
2341 use_relative_line_numbers: None,
2342 disable_expand_excerpt_buttons: !full_mode,
2343 delegate_expand_excerpts: false,
2344 show_git_diff_gutter: None,
2345 show_code_actions: None,
2346 show_runnables: None,
2347 show_breakpoints: None,
2348 show_diff_review_button: false,
2349 show_wrap_guides: None,
2350 show_indent_guides,
2351 buffers_with_disabled_indent_guides: HashSet::default(),
2352 highlight_order: 0,
2353 highlighted_rows: HashMap::default(),
2354 background_highlights: HashMap::default(),
2355 gutter_highlights: HashMap::default(),
2356 scrollbar_marker_state: ScrollbarMarkerState::default(),
2357 active_indent_guides_state: ActiveIndentGuidesState::default(),
2358 nav_history: None,
2359 context_menu: RefCell::new(None),
2360 context_menu_options: None,
2361 mouse_context_menu: None,
2362 completion_tasks: Vec::new(),
2363 inline_blame_popover: None,
2364 inline_blame_popover_show_task: None,
2365 signature_help_state: SignatureHelpState::default(),
2366 auto_signature_help: None,
2367 find_all_references_task_sources: Vec::new(),
2368 next_completion_id: 0,
2369 next_inlay_id: 0,
2370 code_action_providers,
2371 available_code_actions: None,
2372 code_actions_task: None,
2373 quick_selection_highlight_task: None,
2374 debounced_selection_highlight_task: None,
2375 debounced_selection_highlight_complete: false,
2376 document_highlights_task: None,
2377 linked_editing_range_task: None,
2378 pending_rename: None,
2379 searchable: !is_minimap,
2380 cursor_shape: EditorSettings::get_global(cx)
2381 .cursor_shape
2382 .unwrap_or_default(),
2383 cursor_offset_on_selection: false,
2384 current_line_highlight: None,
2385 autoindent_mode: Some(AutoindentMode::EachLine),
2386 collapse_matches: false,
2387 workspace: None,
2388 input_enabled: !is_minimap,
2389 use_modal_editing: full_mode,
2390 read_only: is_minimap,
2391 use_autoclose: true,
2392 use_auto_surround: true,
2393 auto_replace_emoji_shortcode: false,
2394 jsx_tag_auto_close_enabled_in_any_buffer: false,
2395 leader_id: None,
2396 remote_id: None,
2397 hover_state: HoverState::default(),
2398 pending_mouse_down: None,
2399 prev_pressure_stage: None,
2400 hovered_link_state: None,
2401 edit_prediction_provider: None,
2402 active_edit_prediction: None,
2403 stale_edit_prediction_in_menu: None,
2404 edit_prediction_preview: EditPredictionPreview::Inactive {
2405 released_too_fast: false,
2406 },
2407 inline_diagnostics_enabled: full_mode,
2408 diagnostics_enabled: full_mode,
2409 word_completions_enabled: full_mode,
2410 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2411 gutter_hovered: false,
2412 pixel_position_of_newest_cursor: None,
2413 last_bounds: None,
2414 last_position_map: None,
2415 expect_bounds_change: None,
2416 gutter_dimensions: GutterDimensions::default(),
2417 style: None,
2418 show_cursor_names: false,
2419 hovered_cursors: HashMap::default(),
2420 next_editor_action_id: EditorActionId::default(),
2421 editor_actions: Rc::default(),
2422 edit_predictions_hidden_for_vim_mode: false,
2423 show_edit_predictions_override: None,
2424 show_completions_on_input_override: None,
2425 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2426 edit_prediction_settings: EditPredictionSettings::Disabled,
2427 edit_prediction_indent_conflict: false,
2428 edit_prediction_requires_modifier_in_indent_conflict: true,
2429 custom_context_menu: None,
2430 show_git_blame_gutter: false,
2431 show_git_blame_inline: false,
2432 show_selection_menu: None,
2433 show_git_blame_inline_delay_task: None,
2434 git_blame_inline_enabled: full_mode
2435 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2436 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2437 buffer_serialization: is_minimap.not().then(|| {
2438 BufferSerialization::new(
2439 ProjectSettings::get_global(cx)
2440 .session
2441 .restore_unsaved_buffers,
2442 )
2443 }),
2444 blame: None,
2445 blame_subscription: None,
2446 tasks: BTreeMap::default(),
2447
2448 breakpoint_store,
2449 gutter_breakpoint_indicator: (None, None),
2450 gutter_diff_review_indicator: (None, None),
2451 diff_review_overlays: Vec::new(),
2452 stored_review_comments: Vec::new(),
2453 next_review_comment_id: 0,
2454 hovered_diff_hunk_row: None,
2455 _subscriptions: (!is_minimap)
2456 .then(|| {
2457 vec![
2458 cx.observe(&multi_buffer, Self::on_buffer_changed),
2459 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2460 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2461 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2462 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2463 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2464 cx.observe_window_activation(window, |editor, window, cx| {
2465 let active = window.is_window_active();
2466 editor.blink_manager.update(cx, |blink_manager, cx| {
2467 if active {
2468 blink_manager.enable(cx);
2469 } else {
2470 blink_manager.disable(cx);
2471 }
2472 });
2473 if active {
2474 editor.show_mouse_cursor(cx);
2475 }
2476 }),
2477 ]
2478 })
2479 .unwrap_or_default(),
2480 tasks_update_task: None,
2481 pull_diagnostics_task: Task::ready(()),
2482 pull_diagnostics_background_task: Task::ready(()),
2483 colors: None,
2484 refresh_colors_task: Task::ready(()),
2485 inlay_hints: None,
2486 next_color_inlay_id: 0,
2487 post_scroll_update: Task::ready(()),
2488 linked_edit_ranges: Default::default(),
2489 in_project_search: false,
2490 previous_search_ranges: None,
2491 breadcrumb_header: None,
2492 focused_block: None,
2493 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2494 addons: HashMap::default(),
2495 registered_buffers: HashMap::default(),
2496 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2497 selection_mark_mode: false,
2498 toggle_fold_multiple_buffers: Task::ready(()),
2499 serialize_selections: Task::ready(()),
2500 serialize_folds: Task::ready(()),
2501 text_style_refinement: None,
2502 load_diff_task: load_uncommitted_diff,
2503 temporary_diff_override: false,
2504 mouse_cursor_hidden: false,
2505 minimap: None,
2506 hide_mouse_mode: EditorSettings::get_global(cx)
2507 .hide_mouse
2508 .unwrap_or_default(),
2509 change_list: ChangeList::new(),
2510 mode,
2511 selection_drag_state: SelectionDragState::None,
2512 folding_newlines: Task::ready(()),
2513 lookup_key: None,
2514 select_next_is_case_sensitive: None,
2515 applicable_language_settings: HashMap::default(),
2516 accent_data: None,
2517 fetched_tree_sitter_chunks: HashMap::default(),
2518 number_deleted_lines: false,
2519 };
2520
2521 if is_minimap {
2522 return editor;
2523 }
2524
2525 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2526 editor.accent_data = editor.fetch_accent_data(cx);
2527
2528 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2529 editor
2530 ._subscriptions
2531 .push(cx.observe(breakpoints, |_, _, cx| {
2532 cx.notify();
2533 }));
2534 }
2535 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2536 editor._subscriptions.extend(project_subscriptions);
2537
2538 editor._subscriptions.push(cx.subscribe_in(
2539 &cx.entity(),
2540 window,
2541 |editor, _, e: &EditorEvent, window, cx| match e {
2542 EditorEvent::ScrollPositionChanged { local, .. } => {
2543 if *local {
2544 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2545 editor.inline_blame_popover.take();
2546 let new_anchor = editor.scroll_manager.anchor();
2547 let snapshot = editor.snapshot(window, cx);
2548 editor.update_restoration_data(cx, move |data| {
2549 data.scroll_position = (
2550 new_anchor.top_row(snapshot.buffer_snapshot()),
2551 new_anchor.offset,
2552 );
2553 });
2554
2555 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2556 cx.background_executor()
2557 .timer(Duration::from_millis(50))
2558 .await;
2559 editor
2560 .update_in(cx, |editor, window, cx| {
2561 editor.register_visible_buffers(cx);
2562 editor.refresh_colors_for_visible_range(None, window, cx);
2563 editor.refresh_inlay_hints(
2564 InlayHintRefreshReason::NewLinesShown,
2565 cx,
2566 );
2567 editor.colorize_brackets(false, cx);
2568 })
2569 .ok();
2570 });
2571 }
2572 }
2573 EditorEvent::Edited { .. } => {
2574 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2575 .map(|vim_mode| vim_mode.0)
2576 .unwrap_or(false);
2577 if !vim_mode {
2578 let display_map = editor.display_snapshot(cx);
2579 let selections = editor.selections.all_adjusted_display(&display_map);
2580 let pop_state = editor
2581 .change_list
2582 .last()
2583 .map(|previous| {
2584 previous.len() == selections.len()
2585 && previous.iter().enumerate().all(|(ix, p)| {
2586 p.to_display_point(&display_map).row()
2587 == selections[ix].head().row()
2588 })
2589 })
2590 .unwrap_or(false);
2591 let new_positions = selections
2592 .into_iter()
2593 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2594 .collect();
2595 editor
2596 .change_list
2597 .push_to_change_list(pop_state, new_positions);
2598 }
2599 }
2600 _ => (),
2601 },
2602 ));
2603
2604 if let Some(dap_store) = editor
2605 .project
2606 .as_ref()
2607 .map(|project| project.read(cx).dap_store())
2608 {
2609 let weak_editor = cx.weak_entity();
2610
2611 editor
2612 ._subscriptions
2613 .push(
2614 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2615 let session_entity = cx.entity();
2616 weak_editor
2617 .update(cx, |editor, cx| {
2618 editor._subscriptions.push(
2619 cx.subscribe(&session_entity, Self::on_debug_session_event),
2620 );
2621 })
2622 .ok();
2623 }),
2624 );
2625
2626 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2627 editor
2628 ._subscriptions
2629 .push(cx.subscribe(&session, Self::on_debug_session_event));
2630 }
2631 }
2632
2633 // skip adding the initial selection to selection history
2634 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2635 editor.end_selection(window, cx);
2636 editor.selection_history.mode = SelectionHistoryMode::Normal;
2637
2638 editor.scroll_manager.show_scrollbars(window, cx);
2639 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2640
2641 if full_mode {
2642 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2643 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2644
2645 if editor.git_blame_inline_enabled {
2646 editor.start_git_blame_inline(false, window, cx);
2647 }
2648
2649 editor.go_to_active_debug_line(window, cx);
2650
2651 editor.minimap =
2652 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2653 editor.colors = Some(LspColorData::new(cx));
2654 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2655
2656 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2657 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2658 }
2659 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2660 }
2661
2662 editor
2663 }
2664
2665 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2666 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2667 }
2668
2669 pub fn deploy_mouse_context_menu(
2670 &mut self,
2671 position: gpui::Point<Pixels>,
2672 context_menu: Entity<ContextMenu>,
2673 window: &mut Window,
2674 cx: &mut Context<Self>,
2675 ) {
2676 self.mouse_context_menu = Some(MouseContextMenu::new(
2677 self,
2678 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2679 context_menu,
2680 window,
2681 cx,
2682 ));
2683 }
2684
2685 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2686 self.mouse_context_menu
2687 .as_ref()
2688 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2689 }
2690
2691 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2692 if self
2693 .selections
2694 .pending_anchor()
2695 .is_some_and(|pending_selection| {
2696 let snapshot = self.buffer().read(cx).snapshot(cx);
2697 pending_selection.range().includes(range, &snapshot)
2698 })
2699 {
2700 return true;
2701 }
2702
2703 self.selections
2704 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2705 .into_iter()
2706 .any(|selection| {
2707 // This is needed to cover a corner case, if we just check for an existing
2708 // selection in the fold range, having a cursor at the start of the fold
2709 // marks it as selected. Non-empty selections don't cause this.
2710 let length = selection.end - selection.start;
2711 length > 0
2712 })
2713 }
2714
2715 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2716 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2717 }
2718
2719 fn key_context_internal(
2720 &self,
2721 has_active_edit_prediction: bool,
2722 window: &mut Window,
2723 cx: &mut App,
2724 ) -> KeyContext {
2725 let mut key_context = KeyContext::new_with_defaults();
2726 key_context.add("Editor");
2727 let mode = match self.mode {
2728 EditorMode::SingleLine => "single_line",
2729 EditorMode::AutoHeight { .. } => "auto_height",
2730 EditorMode::Minimap { .. } => "minimap",
2731 EditorMode::Full { .. } => "full",
2732 };
2733
2734 if EditorSettings::jupyter_enabled(cx) {
2735 key_context.add("jupyter");
2736 }
2737
2738 key_context.set("mode", mode);
2739 if self.pending_rename.is_some() {
2740 key_context.add("renaming");
2741 }
2742
2743 if let Some(snippet_stack) = self.snippet_stack.last() {
2744 key_context.add("in_snippet");
2745
2746 if snippet_stack.active_index > 0 {
2747 key_context.add("has_previous_tabstop");
2748 }
2749
2750 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2751 key_context.add("has_next_tabstop");
2752 }
2753 }
2754
2755 match self.context_menu.borrow().as_ref() {
2756 Some(CodeContextMenu::Completions(menu)) => {
2757 if menu.visible() {
2758 key_context.add("menu");
2759 key_context.add("showing_completions");
2760 }
2761 }
2762 Some(CodeContextMenu::CodeActions(menu)) => {
2763 if menu.visible() {
2764 key_context.add("menu");
2765 key_context.add("showing_code_actions")
2766 }
2767 }
2768 None => {}
2769 }
2770
2771 if self.signature_help_state.has_multiple_signatures() {
2772 key_context.add("showing_signature_help");
2773 }
2774
2775 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2776 if !self.focus_handle(cx).contains_focused(window, cx)
2777 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2778 {
2779 for addon in self.addons.values() {
2780 addon.extend_key_context(&mut key_context, cx)
2781 }
2782 }
2783
2784 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2785 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2786 Some(
2787 file.full_path(cx)
2788 .extension()?
2789 .to_string_lossy()
2790 .to_lowercase(),
2791 )
2792 }) {
2793 key_context.set("extension", extension);
2794 }
2795 } else {
2796 key_context.add("multibuffer");
2797 }
2798
2799 if has_active_edit_prediction {
2800 if self.edit_prediction_in_conflict() {
2801 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2802 } else {
2803 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2804 key_context.add("copilot_suggestion");
2805 }
2806 }
2807
2808 if self.selection_mark_mode {
2809 key_context.add("selection_mode");
2810 }
2811
2812 let disjoint = self.selections.disjoint_anchors();
2813 let snapshot = self.snapshot(window, cx);
2814 let snapshot = snapshot.buffer_snapshot();
2815 if self.mode == EditorMode::SingleLine
2816 && let [selection] = disjoint
2817 && selection.start == selection.end
2818 && selection.end.to_offset(snapshot) == snapshot.len()
2819 {
2820 key_context.add("end_of_input");
2821 }
2822
2823 if self.has_any_expanded_diff_hunks(cx) {
2824 key_context.add("diffs_expanded");
2825 }
2826
2827 key_context
2828 }
2829
2830 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2831 self.last_bounds.as_ref()
2832 }
2833
2834 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2835 if self.mouse_cursor_hidden {
2836 self.mouse_cursor_hidden = false;
2837 cx.notify();
2838 }
2839 }
2840
2841 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2842 let hide_mouse_cursor = match origin {
2843 HideMouseCursorOrigin::TypingAction => {
2844 matches!(
2845 self.hide_mouse_mode,
2846 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2847 )
2848 }
2849 HideMouseCursorOrigin::MovementAction => {
2850 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2851 }
2852 };
2853 if self.mouse_cursor_hidden != hide_mouse_cursor {
2854 self.mouse_cursor_hidden = hide_mouse_cursor;
2855 cx.notify();
2856 }
2857 }
2858
2859 pub fn edit_prediction_in_conflict(&self) -> bool {
2860 if !self.show_edit_predictions_in_menu() {
2861 return false;
2862 }
2863
2864 let showing_completions = self
2865 .context_menu
2866 .borrow()
2867 .as_ref()
2868 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2869
2870 showing_completions
2871 || self.edit_prediction_requires_modifier()
2872 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2873 // bindings to insert tab characters.
2874 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2875 }
2876
2877 pub fn accept_edit_prediction_keybind(
2878 &self,
2879 granularity: EditPredictionGranularity,
2880 window: &mut Window,
2881 cx: &mut App,
2882 ) -> AcceptEditPredictionBinding {
2883 let key_context = self.key_context_internal(true, window, cx);
2884 let in_conflict = self.edit_prediction_in_conflict();
2885
2886 let bindings =
2887 match granularity {
2888 EditPredictionGranularity::Word => window
2889 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2890 EditPredictionGranularity::Line => window
2891 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2892 EditPredictionGranularity::Full => {
2893 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2894 }
2895 };
2896
2897 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2898 !in_conflict
2899 || binding
2900 .keystrokes()
2901 .first()
2902 .is_some_and(|keystroke| keystroke.modifiers().modified())
2903 }))
2904 }
2905
2906 pub fn new_file(
2907 workspace: &mut Workspace,
2908 _: &workspace::NewFile,
2909 window: &mut Window,
2910 cx: &mut Context<Workspace>,
2911 ) {
2912 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2913 "Failed to create buffer",
2914 window,
2915 cx,
2916 |e, _, _| match e.error_code() {
2917 ErrorCode::RemoteUpgradeRequired => Some(format!(
2918 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2919 e.error_tag("required").unwrap_or("the latest version")
2920 )),
2921 _ => None,
2922 },
2923 );
2924 }
2925
2926 pub fn new_in_workspace(
2927 workspace: &mut Workspace,
2928 window: &mut Window,
2929 cx: &mut Context<Workspace>,
2930 ) -> Task<Result<Entity<Editor>>> {
2931 let project = workspace.project().clone();
2932 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
2933
2934 cx.spawn_in(window, async move |workspace, cx| {
2935 let buffer = create.await?;
2936 workspace.update_in(cx, |workspace, window, cx| {
2937 let editor =
2938 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2939 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2940 editor
2941 })
2942 })
2943 }
2944
2945 fn new_file_vertical(
2946 workspace: &mut Workspace,
2947 _: &workspace::NewFileSplitVertical,
2948 window: &mut Window,
2949 cx: &mut Context<Workspace>,
2950 ) {
2951 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2952 }
2953
2954 fn new_file_horizontal(
2955 workspace: &mut Workspace,
2956 _: &workspace::NewFileSplitHorizontal,
2957 window: &mut Window,
2958 cx: &mut Context<Workspace>,
2959 ) {
2960 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2961 }
2962
2963 fn new_file_split(
2964 workspace: &mut Workspace,
2965 action: &workspace::NewFileSplit,
2966 window: &mut Window,
2967 cx: &mut Context<Workspace>,
2968 ) {
2969 Self::new_file_in_direction(workspace, action.0, window, cx)
2970 }
2971
2972 fn new_file_in_direction(
2973 workspace: &mut Workspace,
2974 direction: SplitDirection,
2975 window: &mut Window,
2976 cx: &mut Context<Workspace>,
2977 ) {
2978 let project = workspace.project().clone();
2979 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
2980
2981 cx.spawn_in(window, async move |workspace, cx| {
2982 let buffer = create.await?;
2983 workspace.update_in(cx, move |workspace, window, cx| {
2984 workspace.split_item(
2985 direction,
2986 Box::new(
2987 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2988 ),
2989 window,
2990 cx,
2991 )
2992 })?;
2993 anyhow::Ok(())
2994 })
2995 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2996 match e.error_code() {
2997 ErrorCode::RemoteUpgradeRequired => Some(format!(
2998 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2999 e.error_tag("required").unwrap_or("the latest version")
3000 )),
3001 _ => None,
3002 }
3003 });
3004 }
3005
3006 pub fn leader_id(&self) -> Option<CollaboratorId> {
3007 self.leader_id
3008 }
3009
3010 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3011 &self.buffer
3012 }
3013
3014 pub fn project(&self) -> Option<&Entity<Project>> {
3015 self.project.as_ref()
3016 }
3017
3018 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3019 self.workspace.as_ref()?.0.upgrade()
3020 }
3021
3022 /// Returns the workspace serialization ID if this editor should be serialized.
3023 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3024 self.workspace
3025 .as_ref()
3026 .filter(|_| self.should_serialize_buffer())
3027 .and_then(|workspace| workspace.1)
3028 }
3029
3030 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3031 self.buffer().read(cx).title(cx)
3032 }
3033
3034 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3035 let git_blame_gutter_max_author_length = self
3036 .render_git_blame_gutter(cx)
3037 .then(|| {
3038 if let Some(blame) = self.blame.as_ref() {
3039 let max_author_length =
3040 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3041 Some(max_author_length)
3042 } else {
3043 None
3044 }
3045 })
3046 .flatten();
3047
3048 EditorSnapshot {
3049 mode: self.mode.clone(),
3050 show_gutter: self.show_gutter,
3051 offset_content: self.offset_content,
3052 show_line_numbers: self.show_line_numbers,
3053 number_deleted_lines: self.number_deleted_lines,
3054 show_git_diff_gutter: self.show_git_diff_gutter,
3055 show_code_actions: self.show_code_actions,
3056 show_runnables: self.show_runnables,
3057 show_breakpoints: self.show_breakpoints,
3058 git_blame_gutter_max_author_length,
3059 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
3060 placeholder_display_snapshot: self
3061 .placeholder_display_map
3062 .as_ref()
3063 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3064 scroll_anchor: self.scroll_manager.anchor(),
3065 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3066 is_focused: self.focus_handle.is_focused(window),
3067 current_line_highlight: self
3068 .current_line_highlight
3069 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3070 gutter_hovered: self.gutter_hovered,
3071 }
3072 }
3073
3074 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3075 self.buffer.read(cx).language_at(point, cx)
3076 }
3077
3078 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3079 self.buffer.read(cx).read(cx).file_at(point).cloned()
3080 }
3081
3082 pub fn active_excerpt(
3083 &self,
3084 cx: &App,
3085 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3086 self.buffer
3087 .read(cx)
3088 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3089 }
3090
3091 pub fn mode(&self) -> &EditorMode {
3092 &self.mode
3093 }
3094
3095 pub fn set_mode(&mut self, mode: EditorMode) {
3096 self.mode = mode;
3097 }
3098
3099 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3100 self.collaboration_hub.as_deref()
3101 }
3102
3103 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3104 self.collaboration_hub = Some(hub);
3105 }
3106
3107 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3108 self.in_project_search = in_project_search;
3109 }
3110
3111 pub fn set_custom_context_menu(
3112 &mut self,
3113 f: impl 'static
3114 + Fn(
3115 &mut Self,
3116 DisplayPoint,
3117 &mut Window,
3118 &mut Context<Self>,
3119 ) -> Option<Entity<ui::ContextMenu>>,
3120 ) {
3121 self.custom_context_menu = Some(Box::new(f))
3122 }
3123
3124 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3125 self.completion_provider = provider;
3126 }
3127
3128 #[cfg(any(test, feature = "test-support"))]
3129 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3130 self.completion_provider.clone()
3131 }
3132
3133 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3134 self.semantics_provider.clone()
3135 }
3136
3137 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3138 self.semantics_provider = provider;
3139 }
3140
3141 pub fn set_edit_prediction_provider<T>(
3142 &mut self,
3143 provider: Option<Entity<T>>,
3144 window: &mut Window,
3145 cx: &mut Context<Self>,
3146 ) where
3147 T: EditPredictionDelegate,
3148 {
3149 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3150 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3151 if this.focus_handle.is_focused(window) {
3152 this.update_visible_edit_prediction(window, cx);
3153 }
3154 }),
3155 provider: Arc::new(provider),
3156 });
3157 self.update_edit_prediction_settings(cx);
3158 self.refresh_edit_prediction(false, false, window, cx);
3159 }
3160
3161 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3162 self.placeholder_display_map
3163 .as_ref()
3164 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3165 }
3166
3167 pub fn set_placeholder_text(
3168 &mut self,
3169 placeholder_text: &str,
3170 window: &mut Window,
3171 cx: &mut Context<Self>,
3172 ) {
3173 let multibuffer = cx
3174 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3175
3176 let style = window.text_style();
3177
3178 self.placeholder_display_map = Some(cx.new(|cx| {
3179 DisplayMap::new(
3180 multibuffer,
3181 style.font(),
3182 style.font_size.to_pixels(window.rem_size()),
3183 None,
3184 FILE_HEADER_HEIGHT,
3185 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3186 Default::default(),
3187 DiagnosticSeverity::Off,
3188 cx,
3189 )
3190 }));
3191 cx.notify();
3192 }
3193
3194 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3195 self.cursor_shape = cursor_shape;
3196
3197 // Disrupt blink for immediate user feedback that the cursor shape has changed
3198 self.blink_manager.update(cx, BlinkManager::show_cursor);
3199
3200 cx.notify();
3201 }
3202
3203 pub fn cursor_shape(&self) -> CursorShape {
3204 self.cursor_shape
3205 }
3206
3207 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3208 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3209 }
3210
3211 pub fn set_current_line_highlight(
3212 &mut self,
3213 current_line_highlight: Option<CurrentLineHighlight>,
3214 ) {
3215 self.current_line_highlight = current_line_highlight;
3216 }
3217
3218 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3219 self.collapse_matches = collapse_matches;
3220 }
3221
3222 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3223 if self.collapse_matches {
3224 return range.start..range.start;
3225 }
3226 range.clone()
3227 }
3228
3229 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3230 self.display_map.read(cx).clip_at_line_ends
3231 }
3232
3233 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3234 if self.display_map.read(cx).clip_at_line_ends != clip {
3235 self.display_map
3236 .update(cx, |map, _| map.clip_at_line_ends = clip);
3237 }
3238 }
3239
3240 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3241 self.input_enabled = input_enabled;
3242 }
3243
3244 pub fn set_edit_predictions_hidden_for_vim_mode(
3245 &mut self,
3246 hidden: bool,
3247 window: &mut Window,
3248 cx: &mut Context<Self>,
3249 ) {
3250 if hidden != self.edit_predictions_hidden_for_vim_mode {
3251 self.edit_predictions_hidden_for_vim_mode = hidden;
3252 if hidden {
3253 self.update_visible_edit_prediction(window, cx);
3254 } else {
3255 self.refresh_edit_prediction(true, false, window, cx);
3256 }
3257 }
3258 }
3259
3260 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3261 self.menu_edit_predictions_policy = value;
3262 }
3263
3264 pub fn set_autoindent(&mut self, autoindent: bool) {
3265 if autoindent {
3266 self.autoindent_mode = Some(AutoindentMode::EachLine);
3267 } else {
3268 self.autoindent_mode = None;
3269 }
3270 }
3271
3272 pub fn capability(&self, cx: &App) -> Capability {
3273 if self.read_only {
3274 Capability::ReadOnly
3275 } else {
3276 self.buffer.read(cx).capability()
3277 }
3278 }
3279
3280 pub fn read_only(&self, cx: &App) -> bool {
3281 self.read_only || self.buffer.read(cx).read_only()
3282 }
3283
3284 pub fn set_read_only(&mut self, read_only: bool) {
3285 self.read_only = read_only;
3286 }
3287
3288 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3289 self.use_autoclose = autoclose;
3290 }
3291
3292 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3293 self.use_auto_surround = auto_surround;
3294 }
3295
3296 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3297 self.auto_replace_emoji_shortcode = auto_replace;
3298 }
3299
3300 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3301 self.buffer_serialization = should_serialize.then(|| {
3302 BufferSerialization::new(
3303 ProjectSettings::get_global(cx)
3304 .session
3305 .restore_unsaved_buffers,
3306 )
3307 })
3308 }
3309
3310 fn should_serialize_buffer(&self) -> bool {
3311 self.buffer_serialization.is_some()
3312 }
3313
3314 pub fn toggle_edit_predictions(
3315 &mut self,
3316 _: &ToggleEditPrediction,
3317 window: &mut Window,
3318 cx: &mut Context<Self>,
3319 ) {
3320 if self.show_edit_predictions_override.is_some() {
3321 self.set_show_edit_predictions(None, window, cx);
3322 } else {
3323 let show_edit_predictions = !self.edit_predictions_enabled();
3324 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3325 }
3326 }
3327
3328 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3329 self.show_completions_on_input_override = show_completions_on_input;
3330 }
3331
3332 pub fn set_show_edit_predictions(
3333 &mut self,
3334 show_edit_predictions: Option<bool>,
3335 window: &mut Window,
3336 cx: &mut Context<Self>,
3337 ) {
3338 self.show_edit_predictions_override = show_edit_predictions;
3339 self.update_edit_prediction_settings(cx);
3340
3341 if let Some(false) = show_edit_predictions {
3342 self.discard_edit_prediction(false, cx);
3343 } else {
3344 self.refresh_edit_prediction(false, true, window, cx);
3345 }
3346 }
3347
3348 fn edit_predictions_disabled_in_scope(
3349 &self,
3350 buffer: &Entity<Buffer>,
3351 buffer_position: language::Anchor,
3352 cx: &App,
3353 ) -> bool {
3354 let snapshot = buffer.read(cx).snapshot();
3355 let settings = snapshot.settings_at(buffer_position, cx);
3356
3357 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3358 return false;
3359 };
3360
3361 scope.override_name().is_some_and(|scope_name| {
3362 settings
3363 .edit_predictions_disabled_in
3364 .iter()
3365 .any(|s| s == scope_name)
3366 })
3367 }
3368
3369 pub fn set_use_modal_editing(&mut self, to: bool) {
3370 self.use_modal_editing = to;
3371 }
3372
3373 pub fn use_modal_editing(&self) -> bool {
3374 self.use_modal_editing
3375 }
3376
3377 fn selections_did_change(
3378 &mut self,
3379 local: bool,
3380 old_cursor_position: &Anchor,
3381 effects: SelectionEffects,
3382 window: &mut Window,
3383 cx: &mut Context<Self>,
3384 ) {
3385 window.invalidate_character_coordinates();
3386
3387 // Copy selections to primary selection buffer
3388 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3389 if local {
3390 let selections = self
3391 .selections
3392 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3393 let buffer_handle = self.buffer.read(cx).read(cx);
3394
3395 let mut text = String::new();
3396 for (index, selection) in selections.iter().enumerate() {
3397 let text_for_selection = buffer_handle
3398 .text_for_range(selection.start..selection.end)
3399 .collect::<String>();
3400
3401 text.push_str(&text_for_selection);
3402 if index != selections.len() - 1 {
3403 text.push('\n');
3404 }
3405 }
3406
3407 if !text.is_empty() {
3408 cx.write_to_primary(ClipboardItem::new_string(text));
3409 }
3410 }
3411
3412 let selection_anchors = self.selections.disjoint_anchors_arc();
3413
3414 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3415 self.buffer.update(cx, |buffer, cx| {
3416 buffer.set_active_selections(
3417 &selection_anchors,
3418 self.selections.line_mode(),
3419 self.cursor_shape,
3420 cx,
3421 )
3422 });
3423 }
3424 let display_map = self
3425 .display_map
3426 .update(cx, |display_map, cx| display_map.snapshot(cx));
3427 let buffer = display_map.buffer_snapshot();
3428 if self.selections.count() == 1 {
3429 self.add_selections_state = None;
3430 }
3431 self.select_next_state = None;
3432 self.select_prev_state = None;
3433 self.select_syntax_node_history.try_clear();
3434 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3435 self.snippet_stack.invalidate(&selection_anchors, buffer);
3436 self.take_rename(false, window, cx);
3437
3438 let newest_selection = self.selections.newest_anchor();
3439 let new_cursor_position = newest_selection.head();
3440 let selection_start = newest_selection.start;
3441
3442 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3443 self.push_to_nav_history(
3444 *old_cursor_position,
3445 Some(new_cursor_position.to_point(buffer)),
3446 false,
3447 effects.nav_history == Some(true),
3448 cx,
3449 );
3450 }
3451
3452 if local {
3453 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3454 self.register_buffer(buffer_id, cx);
3455 }
3456
3457 let mut context_menu = self.context_menu.borrow_mut();
3458 let completion_menu = match context_menu.as_ref() {
3459 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3460 Some(CodeContextMenu::CodeActions(_)) => {
3461 *context_menu = None;
3462 None
3463 }
3464 None => None,
3465 };
3466 let completion_position = completion_menu.map(|menu| menu.initial_position);
3467 drop(context_menu);
3468
3469 if effects.completions
3470 && let Some(completion_position) = completion_position
3471 {
3472 let start_offset = selection_start.to_offset(buffer);
3473 let position_matches = start_offset == completion_position.to_offset(buffer);
3474 let continue_showing = if let Some((snap, ..)) =
3475 buffer.point_to_buffer_offset(completion_position)
3476 && !snap.capability.editable()
3477 {
3478 false
3479 } else if position_matches {
3480 if self.snippet_stack.is_empty() {
3481 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3482 == Some(CharKind::Word)
3483 } else {
3484 // Snippet choices can be shown even when the cursor is in whitespace.
3485 // Dismissing the menu with actions like backspace is handled by
3486 // invalidation regions.
3487 true
3488 }
3489 } else {
3490 false
3491 };
3492
3493 if continue_showing {
3494 self.open_or_update_completions_menu(None, None, false, window, cx);
3495 } else {
3496 self.hide_context_menu(window, cx);
3497 }
3498 }
3499
3500 hide_hover(self, cx);
3501
3502 if old_cursor_position.to_display_point(&display_map).row()
3503 != new_cursor_position.to_display_point(&display_map).row()
3504 {
3505 self.available_code_actions.take();
3506 }
3507 self.refresh_code_actions(window, cx);
3508 self.refresh_document_highlights(cx);
3509 refresh_linked_ranges(self, window, cx);
3510
3511 self.refresh_selected_text_highlights(false, window, cx);
3512 self.refresh_matching_bracket_highlights(window, cx);
3513 self.update_visible_edit_prediction(window, cx);
3514 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3515 self.inline_blame_popover.take();
3516 if self.git_blame_inline_enabled {
3517 self.start_inline_blame_timer(window, cx);
3518 }
3519 }
3520
3521 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3522 cx.emit(EditorEvent::SelectionsChanged { local });
3523
3524 let selections = &self.selections.disjoint_anchors_arc();
3525 if selections.len() == 1 {
3526 cx.emit(SearchEvent::ActiveMatchChanged)
3527 }
3528 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3529 let inmemory_selections = selections
3530 .iter()
3531 .map(|s| {
3532 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3533 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3534 })
3535 .collect();
3536 self.update_restoration_data(cx, |data| {
3537 data.selections = inmemory_selections;
3538 });
3539
3540 if WorkspaceSettings::get(None, cx).restore_on_startup
3541 != RestoreOnStartupBehavior::EmptyTab
3542 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3543 {
3544 let snapshot = self.buffer().read(cx).snapshot(cx);
3545 let selections = selections.clone();
3546 let background_executor = cx.background_executor().clone();
3547 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3548 self.serialize_selections = cx.background_spawn(async move {
3549 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3550 let db_selections = selections
3551 .iter()
3552 .map(|selection| {
3553 (
3554 selection.start.to_offset(&snapshot).0,
3555 selection.end.to_offset(&snapshot).0,
3556 )
3557 })
3558 .collect();
3559
3560 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3561 .await
3562 .with_context(|| {
3563 format!(
3564 "persisting editor selections for editor {editor_id}, \
3565 workspace {workspace_id:?}"
3566 )
3567 })
3568 .log_err();
3569 });
3570 }
3571 }
3572
3573 cx.notify();
3574 }
3575
3576 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3577 use text::ToOffset as _;
3578 use text::ToPoint as _;
3579
3580 if self.mode.is_minimap()
3581 || WorkspaceSettings::get(None, cx).restore_on_startup
3582 == RestoreOnStartupBehavior::EmptyTab
3583 {
3584 return;
3585 }
3586
3587 if !self.buffer().read(cx).is_singleton() {
3588 return;
3589 }
3590
3591 let display_snapshot = self
3592 .display_map
3593 .update(cx, |display_map, cx| display_map.snapshot(cx));
3594 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3595 return;
3596 };
3597 let inmemory_folds = display_snapshot
3598 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3599 .map(|fold| {
3600 fold.range.start.text_anchor.to_point(&snapshot)
3601 ..fold.range.end.text_anchor.to_point(&snapshot)
3602 })
3603 .collect();
3604 self.update_restoration_data(cx, |data| {
3605 data.folds = inmemory_folds;
3606 });
3607
3608 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3609 return;
3610 };
3611 let background_executor = cx.background_executor().clone();
3612 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3613 const FINGERPRINT_LEN: usize = 32;
3614 let db_folds = display_snapshot
3615 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3616 .map(|fold| {
3617 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3618 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3619
3620 // Extract fingerprints - content at fold boundaries for validation on restore
3621 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3622 // content that might change independently.
3623 // start_fp: first min(32, fold_len) bytes of fold content
3624 // end_fp: last min(32, fold_len) bytes of fold content
3625 // Clip to character boundaries to handle multibyte UTF-8 characters.
3626 let fold_len = end - start;
3627 let start_fp_end = snapshot
3628 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3629 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3630 let end_fp_start = snapshot
3631 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3632 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3633
3634 (start, end, start_fp, end_fp)
3635 })
3636 .collect::<Vec<_>>();
3637 self.serialize_folds = cx.background_spawn(async move {
3638 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3639 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3640 .await
3641 .with_context(|| {
3642 format!(
3643 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3644 )
3645 })
3646 .log_err();
3647 });
3648 }
3649
3650 pub fn sync_selections(
3651 &mut self,
3652 other: Entity<Editor>,
3653 cx: &mut Context<Self>,
3654 ) -> gpui::Subscription {
3655 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3656 if !other_selections.is_empty() {
3657 self.selections
3658 .change_with(&self.display_snapshot(cx), |selections| {
3659 selections.select_anchors(other_selections);
3660 });
3661 }
3662
3663 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3664 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3665 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3666 if other_selections.is_empty() {
3667 return;
3668 }
3669 let snapshot = this.display_snapshot(cx);
3670 this.selections.change_with(&snapshot, |selections| {
3671 selections.select_anchors(other_selections);
3672 });
3673 }
3674 });
3675
3676 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3677 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3678 let these_selections = this.selections.disjoint_anchors().to_vec();
3679 if these_selections.is_empty() {
3680 return;
3681 }
3682 other.update(cx, |other_editor, cx| {
3683 let snapshot = other_editor.display_snapshot(cx);
3684 other_editor
3685 .selections
3686 .change_with(&snapshot, |selections| {
3687 selections.select_anchors(these_selections);
3688 })
3689 });
3690 }
3691 });
3692
3693 Subscription::join(other_subscription, this_subscription)
3694 }
3695
3696 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3697 if self.buffer().read(cx).is_singleton() {
3698 return;
3699 }
3700 let snapshot = self.buffer.read(cx).snapshot(cx);
3701 let buffer_ids: HashSet<BufferId> = self
3702 .selections
3703 .disjoint_anchor_ranges()
3704 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3705 .collect();
3706 for buffer_id in buffer_ids {
3707 self.unfold_buffer(buffer_id, cx);
3708 }
3709 }
3710
3711 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3712 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3713 /// effects of selection change occur at the end of the transaction.
3714 pub fn change_selections<R>(
3715 &mut self,
3716 effects: SelectionEffects,
3717 window: &mut Window,
3718 cx: &mut Context<Self>,
3719 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3720 ) -> R {
3721 let snapshot = self.display_snapshot(cx);
3722 if let Some(state) = &mut self.deferred_selection_effects_state {
3723 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3724 state.effects.completions = effects.completions;
3725 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3726 let (changed, result) = self.selections.change_with(&snapshot, change);
3727 state.changed |= changed;
3728 return result;
3729 }
3730 let mut state = DeferredSelectionEffectsState {
3731 changed: false,
3732 effects,
3733 old_cursor_position: self.selections.newest_anchor().head(),
3734 history_entry: SelectionHistoryEntry {
3735 selections: self.selections.disjoint_anchors_arc(),
3736 select_next_state: self.select_next_state.clone(),
3737 select_prev_state: self.select_prev_state.clone(),
3738 add_selections_state: self.add_selections_state.clone(),
3739 },
3740 };
3741 let (changed, result) = self.selections.change_with(&snapshot, change);
3742 state.changed = state.changed || changed;
3743 if self.defer_selection_effects {
3744 self.deferred_selection_effects_state = Some(state);
3745 } else {
3746 self.apply_selection_effects(state, window, cx);
3747 }
3748 result
3749 }
3750
3751 /// Defers the effects of selection change, so that the effects of multiple calls to
3752 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3753 /// to selection history and the state of popovers based on selection position aren't
3754 /// erroneously updated.
3755 pub fn with_selection_effects_deferred<R>(
3756 &mut self,
3757 window: &mut Window,
3758 cx: &mut Context<Self>,
3759 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3760 ) -> R {
3761 let already_deferred = self.defer_selection_effects;
3762 self.defer_selection_effects = true;
3763 let result = update(self, window, cx);
3764 if !already_deferred {
3765 self.defer_selection_effects = false;
3766 if let Some(state) = self.deferred_selection_effects_state.take() {
3767 self.apply_selection_effects(state, window, cx);
3768 }
3769 }
3770 result
3771 }
3772
3773 fn apply_selection_effects(
3774 &mut self,
3775 state: DeferredSelectionEffectsState,
3776 window: &mut Window,
3777 cx: &mut Context<Self>,
3778 ) {
3779 if state.changed {
3780 self.selection_history.push(state.history_entry);
3781
3782 if let Some(autoscroll) = state.effects.scroll {
3783 self.request_autoscroll(autoscroll, cx);
3784 }
3785
3786 let old_cursor_position = &state.old_cursor_position;
3787
3788 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3789
3790 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3791 self.show_signature_help(&ShowSignatureHelp, window, cx);
3792 }
3793 }
3794 }
3795
3796 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3797 where
3798 I: IntoIterator<Item = (Range<S>, T)>,
3799 S: ToOffset,
3800 T: Into<Arc<str>>,
3801 {
3802 if self.read_only(cx) {
3803 return;
3804 }
3805
3806 self.buffer
3807 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3808 }
3809
3810 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3811 where
3812 I: IntoIterator<Item = (Range<S>, T)>,
3813 S: ToOffset,
3814 T: Into<Arc<str>>,
3815 {
3816 if self.read_only(cx) {
3817 return;
3818 }
3819
3820 self.buffer.update(cx, |buffer, cx| {
3821 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3822 });
3823 }
3824
3825 pub fn edit_with_block_indent<I, S, T>(
3826 &mut self,
3827 edits: I,
3828 original_indent_columns: Vec<Option<u32>>,
3829 cx: &mut Context<Self>,
3830 ) where
3831 I: IntoIterator<Item = (Range<S>, T)>,
3832 S: ToOffset,
3833 T: Into<Arc<str>>,
3834 {
3835 if self.read_only(cx) {
3836 return;
3837 }
3838
3839 self.buffer.update(cx, |buffer, cx| {
3840 buffer.edit(
3841 edits,
3842 Some(AutoindentMode::Block {
3843 original_indent_columns,
3844 }),
3845 cx,
3846 )
3847 });
3848 }
3849
3850 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3851 self.hide_context_menu(window, cx);
3852
3853 match phase {
3854 SelectPhase::Begin {
3855 position,
3856 add,
3857 click_count,
3858 } => self.begin_selection(position, add, click_count, window, cx),
3859 SelectPhase::BeginColumnar {
3860 position,
3861 goal_column,
3862 reset,
3863 mode,
3864 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3865 SelectPhase::Extend {
3866 position,
3867 click_count,
3868 } => self.extend_selection(position, click_count, window, cx),
3869 SelectPhase::Update {
3870 position,
3871 goal_column,
3872 scroll_delta,
3873 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3874 SelectPhase::End => self.end_selection(window, cx),
3875 }
3876 }
3877
3878 fn extend_selection(
3879 &mut self,
3880 position: DisplayPoint,
3881 click_count: usize,
3882 window: &mut Window,
3883 cx: &mut Context<Self>,
3884 ) {
3885 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3886 let tail = self
3887 .selections
3888 .newest::<MultiBufferOffset>(&display_map)
3889 .tail();
3890 let click_count = click_count.max(match self.selections.select_mode() {
3891 SelectMode::Character => 1,
3892 SelectMode::Word(_) => 2,
3893 SelectMode::Line(_) => 3,
3894 SelectMode::All => 4,
3895 });
3896 self.begin_selection(position, false, click_count, window, cx);
3897
3898 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3899
3900 let current_selection = match self.selections.select_mode() {
3901 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3902 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3903 };
3904
3905 let mut pending_selection = self
3906 .selections
3907 .pending_anchor()
3908 .cloned()
3909 .expect("extend_selection not called with pending selection");
3910
3911 if pending_selection
3912 .start
3913 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3914 == Ordering::Greater
3915 {
3916 pending_selection.start = current_selection.start;
3917 }
3918 if pending_selection
3919 .end
3920 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3921 == Ordering::Less
3922 {
3923 pending_selection.end = current_selection.end;
3924 pending_selection.reversed = true;
3925 }
3926
3927 let mut pending_mode = self.selections.pending_mode().unwrap();
3928 match &mut pending_mode {
3929 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3930 _ => {}
3931 }
3932
3933 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3934 SelectionEffects::scroll(Autoscroll::fit())
3935 } else {
3936 SelectionEffects::no_scroll()
3937 };
3938
3939 self.change_selections(effects, window, cx, |s| {
3940 s.set_pending(pending_selection.clone(), pending_mode);
3941 s.set_is_extending(true);
3942 });
3943 }
3944
3945 fn begin_selection(
3946 &mut self,
3947 position: DisplayPoint,
3948 add: bool,
3949 click_count: usize,
3950 window: &mut Window,
3951 cx: &mut Context<Self>,
3952 ) {
3953 if !self.focus_handle.is_focused(window) {
3954 self.last_focused_descendant = None;
3955 window.focus(&self.focus_handle, cx);
3956 }
3957
3958 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3959 let buffer = display_map.buffer_snapshot();
3960 let position = display_map.clip_point(position, Bias::Left);
3961
3962 let start;
3963 let end;
3964 let mode;
3965 let mut auto_scroll;
3966 match click_count {
3967 1 => {
3968 start = buffer.anchor_before(position.to_point(&display_map));
3969 end = start;
3970 mode = SelectMode::Character;
3971 auto_scroll = true;
3972 }
3973 2 => {
3974 let position = display_map
3975 .clip_point(position, Bias::Left)
3976 .to_offset(&display_map, Bias::Left);
3977 let (range, _) = buffer.surrounding_word(position, None);
3978 start = buffer.anchor_before(range.start);
3979 end = buffer.anchor_before(range.end);
3980 mode = SelectMode::Word(start..end);
3981 auto_scroll = true;
3982 }
3983 3 => {
3984 let position = display_map
3985 .clip_point(position, Bias::Left)
3986 .to_point(&display_map);
3987 let line_start = display_map.prev_line_boundary(position).0;
3988 let next_line_start = buffer.clip_point(
3989 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3990 Bias::Left,
3991 );
3992 start = buffer.anchor_before(line_start);
3993 end = buffer.anchor_before(next_line_start);
3994 mode = SelectMode::Line(start..end);
3995 auto_scroll = true;
3996 }
3997 _ => {
3998 start = buffer.anchor_before(MultiBufferOffset(0));
3999 end = buffer.anchor_before(buffer.len());
4000 mode = SelectMode::All;
4001 auto_scroll = false;
4002 }
4003 }
4004 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4005
4006 let point_to_delete: Option<usize> = {
4007 let selected_points: Vec<Selection<Point>> =
4008 self.selections.disjoint_in_range(start..end, &display_map);
4009
4010 if !add || click_count > 1 {
4011 None
4012 } else if !selected_points.is_empty() {
4013 Some(selected_points[0].id)
4014 } else {
4015 let clicked_point_already_selected =
4016 self.selections.disjoint_anchors().iter().find(|selection| {
4017 selection.start.to_point(buffer) == start.to_point(buffer)
4018 || selection.end.to_point(buffer) == end.to_point(buffer)
4019 });
4020
4021 clicked_point_already_selected.map(|selection| selection.id)
4022 }
4023 };
4024
4025 let selections_count = self.selections.count();
4026 let effects = if auto_scroll {
4027 SelectionEffects::default()
4028 } else {
4029 SelectionEffects::no_scroll()
4030 };
4031
4032 self.change_selections(effects, window, cx, |s| {
4033 if let Some(point_to_delete) = point_to_delete {
4034 s.delete(point_to_delete);
4035
4036 if selections_count == 1 {
4037 s.set_pending_anchor_range(start..end, mode);
4038 }
4039 } else {
4040 if !add {
4041 s.clear_disjoint();
4042 }
4043
4044 s.set_pending_anchor_range(start..end, mode);
4045 }
4046 });
4047 }
4048
4049 fn begin_columnar_selection(
4050 &mut self,
4051 position: DisplayPoint,
4052 goal_column: u32,
4053 reset: bool,
4054 mode: ColumnarMode,
4055 window: &mut Window,
4056 cx: &mut Context<Self>,
4057 ) {
4058 if !self.focus_handle.is_focused(window) {
4059 self.last_focused_descendant = None;
4060 window.focus(&self.focus_handle, cx);
4061 }
4062
4063 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4064
4065 if reset {
4066 let pointer_position = display_map
4067 .buffer_snapshot()
4068 .anchor_before(position.to_point(&display_map));
4069
4070 self.change_selections(
4071 SelectionEffects::scroll(Autoscroll::newest()),
4072 window,
4073 cx,
4074 |s| {
4075 s.clear_disjoint();
4076 s.set_pending_anchor_range(
4077 pointer_position..pointer_position,
4078 SelectMode::Character,
4079 );
4080 },
4081 );
4082 };
4083
4084 let tail = self.selections.newest::<Point>(&display_map).tail();
4085 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4086 self.columnar_selection_state = match mode {
4087 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4088 selection_tail: selection_anchor,
4089 display_point: if reset {
4090 if position.column() != goal_column {
4091 Some(DisplayPoint::new(position.row(), goal_column))
4092 } else {
4093 None
4094 }
4095 } else {
4096 None
4097 },
4098 }),
4099 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4100 selection_tail: selection_anchor,
4101 }),
4102 };
4103
4104 if !reset {
4105 self.select_columns(position, goal_column, &display_map, window, cx);
4106 }
4107 }
4108
4109 fn update_selection(
4110 &mut self,
4111 position: DisplayPoint,
4112 goal_column: u32,
4113 scroll_delta: gpui::Point<f32>,
4114 window: &mut Window,
4115 cx: &mut Context<Self>,
4116 ) {
4117 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4118
4119 if self.columnar_selection_state.is_some() {
4120 self.select_columns(position, goal_column, &display_map, window, cx);
4121 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4122 let buffer = display_map.buffer_snapshot();
4123 let head;
4124 let tail;
4125 let mode = self.selections.pending_mode().unwrap();
4126 match &mode {
4127 SelectMode::Character => {
4128 head = position.to_point(&display_map);
4129 tail = pending.tail().to_point(buffer);
4130 }
4131 SelectMode::Word(original_range) => {
4132 let offset = display_map
4133 .clip_point(position, Bias::Left)
4134 .to_offset(&display_map, Bias::Left);
4135 let original_range = original_range.to_offset(buffer);
4136
4137 let head_offset = if buffer.is_inside_word(offset, None)
4138 || original_range.contains(&offset)
4139 {
4140 let (word_range, _) = buffer.surrounding_word(offset, None);
4141 if word_range.start < original_range.start {
4142 word_range.start
4143 } else {
4144 word_range.end
4145 }
4146 } else {
4147 offset
4148 };
4149
4150 head = head_offset.to_point(buffer);
4151 if head_offset <= original_range.start {
4152 tail = original_range.end.to_point(buffer);
4153 } else {
4154 tail = original_range.start.to_point(buffer);
4155 }
4156 }
4157 SelectMode::Line(original_range) => {
4158 let original_range = original_range.to_point(display_map.buffer_snapshot());
4159
4160 let position = display_map
4161 .clip_point(position, Bias::Left)
4162 .to_point(&display_map);
4163 let line_start = display_map.prev_line_boundary(position).0;
4164 let next_line_start = buffer.clip_point(
4165 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4166 Bias::Left,
4167 );
4168
4169 if line_start < original_range.start {
4170 head = line_start
4171 } else {
4172 head = next_line_start
4173 }
4174
4175 if head <= original_range.start {
4176 tail = original_range.end;
4177 } else {
4178 tail = original_range.start;
4179 }
4180 }
4181 SelectMode::All => {
4182 return;
4183 }
4184 };
4185
4186 if head < tail {
4187 pending.start = buffer.anchor_before(head);
4188 pending.end = buffer.anchor_before(tail);
4189 pending.reversed = true;
4190 } else {
4191 pending.start = buffer.anchor_before(tail);
4192 pending.end = buffer.anchor_before(head);
4193 pending.reversed = false;
4194 }
4195
4196 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4197 s.set_pending(pending.clone(), mode);
4198 });
4199 } else {
4200 log::error!("update_selection dispatched with no pending selection");
4201 return;
4202 }
4203
4204 self.apply_scroll_delta(scroll_delta, window, cx);
4205 cx.notify();
4206 }
4207
4208 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4209 self.columnar_selection_state.take();
4210 if let Some(pending_mode) = self.selections.pending_mode() {
4211 let selections = self
4212 .selections
4213 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4214 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4215 s.select(selections);
4216 s.clear_pending();
4217 if s.is_extending() {
4218 s.set_is_extending(false);
4219 } else {
4220 s.set_select_mode(pending_mode);
4221 }
4222 });
4223 }
4224 }
4225
4226 fn select_columns(
4227 &mut self,
4228 head: DisplayPoint,
4229 goal_column: u32,
4230 display_map: &DisplaySnapshot,
4231 window: &mut Window,
4232 cx: &mut Context<Self>,
4233 ) {
4234 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4235 return;
4236 };
4237
4238 let tail = match columnar_state {
4239 ColumnarSelectionState::FromMouse {
4240 selection_tail,
4241 display_point,
4242 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4243 ColumnarSelectionState::FromSelection { selection_tail } => {
4244 selection_tail.to_display_point(display_map)
4245 }
4246 };
4247
4248 let start_row = cmp::min(tail.row(), head.row());
4249 let end_row = cmp::max(tail.row(), head.row());
4250 let start_column = cmp::min(tail.column(), goal_column);
4251 let end_column = cmp::max(tail.column(), goal_column);
4252 let reversed = start_column < tail.column();
4253
4254 let selection_ranges = (start_row.0..=end_row.0)
4255 .map(DisplayRow)
4256 .filter_map(|row| {
4257 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4258 || start_column <= display_map.line_len(row))
4259 && !display_map.is_block_line(row)
4260 {
4261 let start = display_map
4262 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4263 .to_point(display_map);
4264 let end = display_map
4265 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4266 .to_point(display_map);
4267 if reversed {
4268 Some(end..start)
4269 } else {
4270 Some(start..end)
4271 }
4272 } else {
4273 None
4274 }
4275 })
4276 .collect::<Vec<_>>();
4277 if selection_ranges.is_empty() {
4278 return;
4279 }
4280
4281 let ranges = match columnar_state {
4282 ColumnarSelectionState::FromMouse { .. } => {
4283 let mut non_empty_ranges = selection_ranges
4284 .iter()
4285 .filter(|selection_range| selection_range.start != selection_range.end)
4286 .peekable();
4287 if non_empty_ranges.peek().is_some() {
4288 non_empty_ranges.cloned().collect()
4289 } else {
4290 selection_ranges
4291 }
4292 }
4293 _ => selection_ranges,
4294 };
4295
4296 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4297 s.select_ranges(ranges);
4298 });
4299 cx.notify();
4300 }
4301
4302 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4303 self.selections
4304 .all_adjusted(snapshot)
4305 .iter()
4306 .any(|selection| !selection.is_empty())
4307 }
4308
4309 pub fn has_pending_nonempty_selection(&self) -> bool {
4310 let pending_nonempty_selection = match self.selections.pending_anchor() {
4311 Some(Selection { start, end, .. }) => start != end,
4312 None => false,
4313 };
4314
4315 pending_nonempty_selection
4316 || (self.columnar_selection_state.is_some()
4317 && self.selections.disjoint_anchors().len() > 1)
4318 }
4319
4320 pub fn has_pending_selection(&self) -> bool {
4321 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4322 }
4323
4324 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4325 self.selection_mark_mode = false;
4326 self.selection_drag_state = SelectionDragState::None;
4327
4328 if self.dismiss_menus_and_popups(true, window, cx) {
4329 cx.notify();
4330 return;
4331 }
4332 if self.clear_expanded_diff_hunks(cx) {
4333 cx.notify();
4334 return;
4335 }
4336 if self.show_git_blame_gutter {
4337 self.show_git_blame_gutter = false;
4338 cx.notify();
4339 return;
4340 }
4341
4342 if self.mode.is_full()
4343 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4344 {
4345 cx.notify();
4346 return;
4347 }
4348
4349 cx.propagate();
4350 }
4351
4352 pub fn dismiss_menus_and_popups(
4353 &mut self,
4354 is_user_requested: bool,
4355 window: &mut Window,
4356 cx: &mut Context<Self>,
4357 ) -> bool {
4358 let mut dismissed = false;
4359
4360 dismissed |= self.take_rename(false, window, cx).is_some();
4361 dismissed |= self.hide_blame_popover(true, cx);
4362 dismissed |= hide_hover(self, cx);
4363 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4364 dismissed |= self.hide_context_menu(window, cx).is_some();
4365 dismissed |= self.mouse_context_menu.take().is_some();
4366 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4367 dismissed |= self.snippet_stack.pop().is_some();
4368 if !self.diff_review_overlays.is_empty() {
4369 self.dismiss_all_diff_review_overlays(cx);
4370 dismissed = true;
4371 }
4372
4373 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4374 self.dismiss_diagnostics(cx);
4375 dismissed = true;
4376 }
4377
4378 dismissed
4379 }
4380
4381 fn linked_editing_ranges_for(
4382 &self,
4383 selection: Range<text::Anchor>,
4384 cx: &App,
4385 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4386 if self.linked_edit_ranges.is_empty() {
4387 return None;
4388 }
4389 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4390 selection.end.buffer_id.and_then(|end_buffer_id| {
4391 if selection.start.buffer_id != Some(end_buffer_id) {
4392 return None;
4393 }
4394 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4395 let snapshot = buffer.read(cx).snapshot();
4396 self.linked_edit_ranges
4397 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4398 .map(|ranges| (ranges, snapshot, buffer))
4399 })?;
4400 use text::ToOffset as TO;
4401 // find offset from the start of current range to current cursor position
4402 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4403
4404 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4405 let start_difference = start_offset - start_byte_offset;
4406 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4407 let end_difference = end_offset - start_byte_offset;
4408 // Current range has associated linked ranges.
4409 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4410 for range in linked_ranges.iter() {
4411 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4412 let end_offset = start_offset + end_difference;
4413 let start_offset = start_offset + start_difference;
4414 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4415 continue;
4416 }
4417 if self.selections.disjoint_anchor_ranges().any(|s| {
4418 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4419 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4420 {
4421 return false;
4422 }
4423 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4424 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4425 }) {
4426 continue;
4427 }
4428 let start = buffer_snapshot.anchor_after(start_offset);
4429 let end = buffer_snapshot.anchor_after(end_offset);
4430 linked_edits
4431 .entry(buffer.clone())
4432 .or_default()
4433 .push(start..end);
4434 }
4435 Some(linked_edits)
4436 }
4437
4438 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4439 let text: Arc<str> = text.into();
4440
4441 if self.read_only(cx) {
4442 return;
4443 }
4444
4445 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4446
4447 self.unfold_buffers_with_selections(cx);
4448
4449 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4450 let mut bracket_inserted = false;
4451 let mut edits = Vec::new();
4452 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4453 let mut new_selections = Vec::with_capacity(selections.len());
4454 let mut new_autoclose_regions = Vec::new();
4455 let snapshot = self.buffer.read(cx).read(cx);
4456 let mut clear_linked_edit_ranges = false;
4457 let mut all_selections_read_only = true;
4458 let mut has_adjacent_edits = false;
4459 let mut in_adjacent_group = false;
4460
4461 let mut regions = self
4462 .selections_with_autoclose_regions(selections, &snapshot)
4463 .peekable();
4464
4465 while let Some((selection, autoclose_region)) = regions.next() {
4466 if snapshot
4467 .point_to_buffer_point(selection.head())
4468 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4469 {
4470 continue;
4471 }
4472 if snapshot
4473 .point_to_buffer_point(selection.tail())
4474 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4475 {
4476 // note, ideally we'd clip the tail to the closest writeable region towards the head
4477 continue;
4478 }
4479 all_selections_read_only = false;
4480
4481 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4482 // Determine if the inserted text matches the opening or closing
4483 // bracket of any of this language's bracket pairs.
4484 let mut bracket_pair = None;
4485 let mut is_bracket_pair_start = false;
4486 let mut is_bracket_pair_end = false;
4487 if !text.is_empty() {
4488 let mut bracket_pair_matching_end = None;
4489 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4490 // and they are removing the character that triggered IME popup.
4491 for (pair, enabled) in scope.brackets() {
4492 if !pair.close && !pair.surround {
4493 continue;
4494 }
4495
4496 if enabled && pair.start.ends_with(text.as_ref()) {
4497 let prefix_len = pair.start.len() - text.len();
4498 let preceding_text_matches_prefix = prefix_len == 0
4499 || (selection.start.column >= (prefix_len as u32)
4500 && snapshot.contains_str_at(
4501 Point::new(
4502 selection.start.row,
4503 selection.start.column - (prefix_len as u32),
4504 ),
4505 &pair.start[..prefix_len],
4506 ));
4507 if preceding_text_matches_prefix {
4508 bracket_pair = Some(pair.clone());
4509 is_bracket_pair_start = true;
4510 break;
4511 }
4512 }
4513 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4514 {
4515 // take first bracket pair matching end, but don't break in case a later bracket
4516 // pair matches start
4517 bracket_pair_matching_end = Some(pair.clone());
4518 }
4519 }
4520 if let Some(end) = bracket_pair_matching_end
4521 && bracket_pair.is_none()
4522 {
4523 bracket_pair = Some(end);
4524 is_bracket_pair_end = true;
4525 }
4526 }
4527
4528 if let Some(bracket_pair) = bracket_pair {
4529 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4530 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4531 let auto_surround =
4532 self.use_auto_surround && snapshot_settings.use_auto_surround;
4533 if selection.is_empty() {
4534 if is_bracket_pair_start {
4535 // If the inserted text is a suffix of an opening bracket and the
4536 // selection is preceded by the rest of the opening bracket, then
4537 // insert the closing bracket.
4538 let following_text_allows_autoclose = snapshot
4539 .chars_at(selection.start)
4540 .next()
4541 .is_none_or(|c| scope.should_autoclose_before(c));
4542
4543 let preceding_text_allows_autoclose = selection.start.column == 0
4544 || snapshot
4545 .reversed_chars_at(selection.start)
4546 .next()
4547 .is_none_or(|c| {
4548 bracket_pair.start != bracket_pair.end
4549 || !snapshot
4550 .char_classifier_at(selection.start)
4551 .is_word(c)
4552 });
4553
4554 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4555 && bracket_pair.start.len() == 1
4556 {
4557 let target = bracket_pair.start.chars().next().unwrap();
4558 let mut byte_offset = 0u32;
4559 let current_line_count = snapshot
4560 .reversed_chars_at(selection.start)
4561 .take_while(|&c| c != '\n')
4562 .filter(|c| {
4563 byte_offset += c.len_utf8() as u32;
4564 if *c != target {
4565 return false;
4566 }
4567
4568 let point = Point::new(
4569 selection.start.row,
4570 selection.start.column.saturating_sub(byte_offset),
4571 );
4572
4573 let is_enabled = snapshot
4574 .language_scope_at(point)
4575 .and_then(|scope| {
4576 scope
4577 .brackets()
4578 .find(|(pair, _)| {
4579 pair.start == bracket_pair.start
4580 })
4581 .map(|(_, enabled)| enabled)
4582 })
4583 .unwrap_or(true);
4584
4585 let is_delimiter = snapshot
4586 .language_scope_at(Point::new(
4587 point.row,
4588 point.column + 1,
4589 ))
4590 .and_then(|scope| {
4591 scope
4592 .brackets()
4593 .find(|(pair, _)| {
4594 pair.start == bracket_pair.start
4595 })
4596 .map(|(_, enabled)| !enabled)
4597 })
4598 .unwrap_or(false);
4599
4600 is_enabled && !is_delimiter
4601 })
4602 .count();
4603 current_line_count % 2 == 1
4604 } else {
4605 false
4606 };
4607
4608 if autoclose
4609 && bracket_pair.close
4610 && following_text_allows_autoclose
4611 && preceding_text_allows_autoclose
4612 && !is_closing_quote
4613 {
4614 let anchor = snapshot.anchor_before(selection.end);
4615 new_selections.push((selection.map(|_| anchor), text.len()));
4616 new_autoclose_regions.push((
4617 anchor,
4618 text.len(),
4619 selection.id,
4620 bracket_pair.clone(),
4621 ));
4622 edits.push((
4623 selection.range(),
4624 format!("{}{}", text, bracket_pair.end).into(),
4625 ));
4626 bracket_inserted = true;
4627 continue;
4628 }
4629 }
4630
4631 if let Some(region) = autoclose_region {
4632 // If the selection is followed by an auto-inserted closing bracket,
4633 // then don't insert that closing bracket again; just move the selection
4634 // past the closing bracket.
4635 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4636 && text.as_ref() == region.pair.end.as_str()
4637 && snapshot.contains_str_at(region.range.end, text.as_ref());
4638 if should_skip {
4639 let anchor = snapshot.anchor_after(selection.end);
4640 new_selections
4641 .push((selection.map(|_| anchor), region.pair.end.len()));
4642 continue;
4643 }
4644 }
4645
4646 let always_treat_brackets_as_autoclosed = snapshot
4647 .language_settings_at(selection.start, cx)
4648 .always_treat_brackets_as_autoclosed;
4649 if always_treat_brackets_as_autoclosed
4650 && is_bracket_pair_end
4651 && snapshot.contains_str_at(selection.end, text.as_ref())
4652 {
4653 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4654 // and the inserted text is a closing bracket and the selection is followed
4655 // by the closing bracket then move the selection past the closing bracket.
4656 let anchor = snapshot.anchor_after(selection.end);
4657 new_selections.push((selection.map(|_| anchor), text.len()));
4658 continue;
4659 }
4660 }
4661 // If an opening bracket is 1 character long and is typed while
4662 // text is selected, then surround that text with the bracket pair.
4663 else if auto_surround
4664 && bracket_pair.surround
4665 && is_bracket_pair_start
4666 && bracket_pair.start.chars().count() == 1
4667 {
4668 edits.push((selection.start..selection.start, text.clone()));
4669 edits.push((
4670 selection.end..selection.end,
4671 bracket_pair.end.as_str().into(),
4672 ));
4673 bracket_inserted = true;
4674 new_selections.push((
4675 Selection {
4676 id: selection.id,
4677 start: snapshot.anchor_after(selection.start),
4678 end: snapshot.anchor_before(selection.end),
4679 reversed: selection.reversed,
4680 goal: selection.goal,
4681 },
4682 0,
4683 ));
4684 continue;
4685 }
4686 }
4687 }
4688
4689 if self.auto_replace_emoji_shortcode
4690 && selection.is_empty()
4691 && text.as_ref().ends_with(':')
4692 && let Some(possible_emoji_short_code) =
4693 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4694 && !possible_emoji_short_code.is_empty()
4695 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4696 {
4697 let emoji_shortcode_start = Point::new(
4698 selection.start.row,
4699 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4700 );
4701
4702 // Remove shortcode from buffer
4703 edits.push((
4704 emoji_shortcode_start..selection.start,
4705 "".to_string().into(),
4706 ));
4707 new_selections.push((
4708 Selection {
4709 id: selection.id,
4710 start: snapshot.anchor_after(emoji_shortcode_start),
4711 end: snapshot.anchor_before(selection.start),
4712 reversed: selection.reversed,
4713 goal: selection.goal,
4714 },
4715 0,
4716 ));
4717
4718 // Insert emoji
4719 let selection_start_anchor = snapshot.anchor_after(selection.start);
4720 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4721 edits.push((selection.start..selection.end, emoji.to_string().into()));
4722
4723 continue;
4724 }
4725
4726 let next_is_adjacent = regions
4727 .peek()
4728 .is_some_and(|(next, _)| selection.end == next.start);
4729
4730 // If not handling any auto-close operation, then just replace the selected
4731 // text with the given input and move the selection to the end of the
4732 // newly inserted text.
4733 let anchor = if in_adjacent_group || next_is_adjacent {
4734 // After edits the right bias would shift those anchor to the next visible fragment
4735 // but we want to resolve to the previous one
4736 snapshot.anchor_before(selection.end)
4737 } else {
4738 snapshot.anchor_after(selection.end)
4739 };
4740
4741 if !self.linked_edit_ranges.is_empty() {
4742 let start_anchor = snapshot.anchor_before(selection.start);
4743
4744 let is_word_char = text.chars().next().is_none_or(|char| {
4745 let classifier = snapshot
4746 .char_classifier_at(start_anchor.to_offset(&snapshot))
4747 .scope_context(Some(CharScopeContext::LinkedEdit));
4748 classifier.is_word(char)
4749 });
4750
4751 if is_word_char {
4752 if let Some(ranges) = self
4753 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4754 {
4755 for (buffer, edits) in ranges {
4756 linked_edits
4757 .entry(buffer.clone())
4758 .or_default()
4759 .extend(edits.into_iter().map(|range| (range, text.clone())));
4760 }
4761 }
4762 } else {
4763 clear_linked_edit_ranges = true;
4764 }
4765 }
4766
4767 new_selections.push((selection.map(|_| anchor), 0));
4768 edits.push((selection.start..selection.end, text.clone()));
4769
4770 has_adjacent_edits |= next_is_adjacent;
4771 in_adjacent_group = next_is_adjacent;
4772 }
4773
4774 if all_selections_read_only {
4775 return;
4776 }
4777
4778 drop(regions);
4779 drop(snapshot);
4780
4781 self.transact(window, cx, |this, window, cx| {
4782 if clear_linked_edit_ranges {
4783 this.linked_edit_ranges.clear();
4784 }
4785 let initial_buffer_versions =
4786 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4787
4788 this.buffer.update(cx, |buffer, cx| {
4789 if has_adjacent_edits {
4790 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4791 } else {
4792 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4793 }
4794 });
4795 for (buffer, edits) in linked_edits {
4796 buffer.update(cx, |buffer, cx| {
4797 let snapshot = buffer.snapshot();
4798 let edits = edits
4799 .into_iter()
4800 .map(|(range, text)| {
4801 use text::ToPoint as TP;
4802 let end_point = TP::to_point(&range.end, &snapshot);
4803 let start_point = TP::to_point(&range.start, &snapshot);
4804 (start_point..end_point, text)
4805 })
4806 .sorted_by_key(|(range, _)| range.start);
4807 buffer.edit(edits, None, cx);
4808 })
4809 }
4810 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4811 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4812 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4813 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4814 new_anchor_selections,
4815 &map,
4816 )
4817 .zip(new_selection_deltas)
4818 .map(|(selection, delta)| Selection {
4819 id: selection.id,
4820 start: selection.start + delta,
4821 end: selection.end + delta,
4822 reversed: selection.reversed,
4823 goal: SelectionGoal::None,
4824 })
4825 .collect::<Vec<_>>();
4826
4827 let mut i = 0;
4828 for (position, delta, selection_id, pair) in new_autoclose_regions {
4829 let position = position.to_offset(map.buffer_snapshot()) + delta;
4830 let start = map.buffer_snapshot().anchor_before(position);
4831 let end = map.buffer_snapshot().anchor_after(position);
4832 while let Some(existing_state) = this.autoclose_regions.get(i) {
4833 match existing_state
4834 .range
4835 .start
4836 .cmp(&start, map.buffer_snapshot())
4837 {
4838 Ordering::Less => i += 1,
4839 Ordering::Greater => break,
4840 Ordering::Equal => {
4841 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4842 Ordering::Less => i += 1,
4843 Ordering::Equal => break,
4844 Ordering::Greater => break,
4845 }
4846 }
4847 }
4848 }
4849 this.autoclose_regions.insert(
4850 i,
4851 AutocloseRegion {
4852 selection_id,
4853 range: start..end,
4854 pair,
4855 },
4856 );
4857 }
4858
4859 let had_active_edit_prediction = this.has_active_edit_prediction();
4860 this.change_selections(
4861 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4862 window,
4863 cx,
4864 |s| s.select(new_selections),
4865 );
4866
4867 if !bracket_inserted
4868 && let Some(on_type_format_task) =
4869 this.trigger_on_type_formatting(text.to_string(), window, cx)
4870 {
4871 on_type_format_task.detach_and_log_err(cx);
4872 }
4873
4874 let editor_settings = EditorSettings::get_global(cx);
4875 if bracket_inserted
4876 && (editor_settings.auto_signature_help
4877 || editor_settings.show_signature_help_after_edits)
4878 {
4879 this.show_signature_help(&ShowSignatureHelp, window, cx);
4880 }
4881
4882 let trigger_in_words =
4883 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4884 if this.hard_wrap.is_some() {
4885 let latest: Range<Point> = this.selections.newest(&map).range();
4886 if latest.is_empty()
4887 && this
4888 .buffer()
4889 .read(cx)
4890 .snapshot(cx)
4891 .line_len(MultiBufferRow(latest.start.row))
4892 == latest.start.column
4893 {
4894 this.rewrap_impl(
4895 RewrapOptions {
4896 override_language_settings: true,
4897 preserve_existing_whitespace: true,
4898 },
4899 cx,
4900 )
4901 }
4902 }
4903 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4904 refresh_linked_ranges(this, window, cx);
4905 this.refresh_edit_prediction(true, false, window, cx);
4906 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4907 });
4908 }
4909
4910 fn find_possible_emoji_shortcode_at_position(
4911 snapshot: &MultiBufferSnapshot,
4912 position: Point,
4913 ) -> Option<String> {
4914 let mut chars = Vec::new();
4915 let mut found_colon = false;
4916 for char in snapshot.reversed_chars_at(position).take(100) {
4917 // Found a possible emoji shortcode in the middle of the buffer
4918 if found_colon {
4919 if char.is_whitespace() {
4920 chars.reverse();
4921 return Some(chars.iter().collect());
4922 }
4923 // If the previous character is not a whitespace, we are in the middle of a word
4924 // and we only want to complete the shortcode if the word is made up of other emojis
4925 let mut containing_word = String::new();
4926 for ch in snapshot
4927 .reversed_chars_at(position)
4928 .skip(chars.len() + 1)
4929 .take(100)
4930 {
4931 if ch.is_whitespace() {
4932 break;
4933 }
4934 containing_word.push(ch);
4935 }
4936 let containing_word = containing_word.chars().rev().collect::<String>();
4937 if util::word_consists_of_emojis(containing_word.as_str()) {
4938 chars.reverse();
4939 return Some(chars.iter().collect());
4940 }
4941 }
4942
4943 if char.is_whitespace() || !char.is_ascii() {
4944 return None;
4945 }
4946 if char == ':' {
4947 found_colon = true;
4948 } else {
4949 chars.push(char);
4950 }
4951 }
4952 // Found a possible emoji shortcode at the beginning of the buffer
4953 chars.reverse();
4954 Some(chars.iter().collect())
4955 }
4956
4957 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4958 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4959 self.transact(window, cx, |this, window, cx| {
4960 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4961 let selections = this
4962 .selections
4963 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4964 let multi_buffer = this.buffer.read(cx);
4965 let buffer = multi_buffer.snapshot(cx);
4966 selections
4967 .iter()
4968 .map(|selection| {
4969 let start_point = selection.start.to_point(&buffer);
4970 let mut existing_indent =
4971 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4972 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4973 let start = selection.start;
4974 let end = selection.end;
4975 let selection_is_empty = start == end;
4976 let language_scope = buffer.language_scope_at(start);
4977 let (delimiter, newline_config) = if let Some(language) = &language_scope {
4978 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
4979 &buffer,
4980 start..end,
4981 language,
4982 )
4983 || NewlineConfig::insert_extra_newline_tree_sitter(
4984 &buffer,
4985 start..end,
4986 );
4987
4988 let mut newline_config = NewlineConfig::Newline {
4989 additional_indent: IndentSize::spaces(0),
4990 extra_line_additional_indent: if needs_extra_newline {
4991 Some(IndentSize::spaces(0))
4992 } else {
4993 None
4994 },
4995 prevent_auto_indent: false,
4996 };
4997
4998 let comment_delimiter = maybe!({
4999 if !selection_is_empty {
5000 return None;
5001 }
5002
5003 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5004 return None;
5005 }
5006
5007 return comment_delimiter_for_newline(
5008 &start_point,
5009 &buffer,
5010 language,
5011 );
5012 });
5013
5014 let doc_delimiter = maybe!({
5015 if !selection_is_empty {
5016 return None;
5017 }
5018
5019 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5020 return None;
5021 }
5022
5023 return documentation_delimiter_for_newline(
5024 &start_point,
5025 &buffer,
5026 language,
5027 &mut newline_config,
5028 );
5029 });
5030
5031 let list_delimiter = maybe!({
5032 if !selection_is_empty {
5033 return None;
5034 }
5035
5036 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5037 return None;
5038 }
5039
5040 return list_delimiter_for_newline(
5041 &start_point,
5042 &buffer,
5043 language,
5044 &mut newline_config,
5045 );
5046 });
5047
5048 (
5049 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5050 newline_config,
5051 )
5052 } else {
5053 (
5054 None,
5055 NewlineConfig::Newline {
5056 additional_indent: IndentSize::spaces(0),
5057 extra_line_additional_indent: None,
5058 prevent_auto_indent: false,
5059 },
5060 )
5061 };
5062
5063 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5064 NewlineConfig::ClearCurrentLine => {
5065 let row_start =
5066 buffer.point_to_offset(Point::new(start_point.row, 0));
5067 (row_start, String::new(), false)
5068 }
5069 NewlineConfig::UnindentCurrentLine { continuation } => {
5070 let row_start =
5071 buffer.point_to_offset(Point::new(start_point.row, 0));
5072 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5073 let tab_size_indent = IndentSize::spaces(tab_size.get());
5074 let reduced_indent =
5075 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5076 let mut new_text = String::new();
5077 new_text.extend(reduced_indent.chars());
5078 new_text.push_str(continuation);
5079 (row_start, new_text, true)
5080 }
5081 NewlineConfig::Newline {
5082 additional_indent,
5083 extra_line_additional_indent,
5084 prevent_auto_indent,
5085 } => {
5086 let capacity_for_delimiter =
5087 delimiter.as_deref().map(str::len).unwrap_or_default();
5088 let extra_line_len = extra_line_additional_indent
5089 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5090 .unwrap_or(0);
5091 let mut new_text = String::with_capacity(
5092 1 + capacity_for_delimiter
5093 + existing_indent.len as usize
5094 + additional_indent.len as usize
5095 + extra_line_len,
5096 );
5097 new_text.push('\n');
5098 new_text.extend(existing_indent.chars());
5099 new_text.extend(additional_indent.chars());
5100 if let Some(delimiter) = &delimiter {
5101 new_text.push_str(delimiter);
5102 }
5103 if let Some(extra_indent) = extra_line_additional_indent {
5104 new_text.push('\n');
5105 new_text.extend(existing_indent.chars());
5106 new_text.extend(extra_indent.chars());
5107 }
5108 (start, new_text, *prevent_auto_indent)
5109 }
5110 };
5111
5112 let anchor = buffer.anchor_after(end);
5113 let new_selection = selection.map(|_| anchor);
5114 (
5115 ((edit_start..end, new_text), prevent_auto_indent),
5116 (newline_config.has_extra_line(), new_selection),
5117 )
5118 })
5119 .unzip()
5120 };
5121
5122 let mut auto_indent_edits = Vec::new();
5123 let mut edits = Vec::new();
5124 for (edit, prevent_auto_indent) in edits_with_flags {
5125 if prevent_auto_indent {
5126 edits.push(edit);
5127 } else {
5128 auto_indent_edits.push(edit);
5129 }
5130 }
5131 if !edits.is_empty() {
5132 this.edit(edits, cx);
5133 }
5134 if !auto_indent_edits.is_empty() {
5135 this.edit_with_autoindent(auto_indent_edits, cx);
5136 }
5137
5138 let buffer = this.buffer.read(cx).snapshot(cx);
5139 let new_selections = selection_info
5140 .into_iter()
5141 .map(|(extra_newline_inserted, new_selection)| {
5142 let mut cursor = new_selection.end.to_point(&buffer);
5143 if extra_newline_inserted {
5144 cursor.row -= 1;
5145 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5146 }
5147 new_selection.map(|_| cursor)
5148 })
5149 .collect();
5150
5151 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5152 this.refresh_edit_prediction(true, false, window, cx);
5153 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5154 task.detach_and_log_err(cx);
5155 }
5156 });
5157 }
5158
5159 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5160 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5161
5162 let buffer = self.buffer.read(cx);
5163 let snapshot = buffer.snapshot(cx);
5164
5165 let mut edits = Vec::new();
5166 let mut rows = Vec::new();
5167
5168 for (rows_inserted, selection) in self
5169 .selections
5170 .all_adjusted(&self.display_snapshot(cx))
5171 .into_iter()
5172 .enumerate()
5173 {
5174 let cursor = selection.head();
5175 let row = cursor.row;
5176
5177 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5178
5179 let newline = "\n".to_string();
5180 edits.push((start_of_line..start_of_line, newline));
5181
5182 rows.push(row + rows_inserted as u32);
5183 }
5184
5185 self.transact(window, cx, |editor, window, cx| {
5186 editor.edit(edits, cx);
5187
5188 editor.change_selections(Default::default(), window, cx, |s| {
5189 let mut index = 0;
5190 s.move_cursors_with(|map, _, _| {
5191 let row = rows[index];
5192 index += 1;
5193
5194 let point = Point::new(row, 0);
5195 let boundary = map.next_line_boundary(point).1;
5196 let clipped = map.clip_point(boundary, Bias::Left);
5197
5198 (clipped, SelectionGoal::None)
5199 });
5200 });
5201
5202 let mut indent_edits = Vec::new();
5203 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5204 for row in rows {
5205 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5206 for (row, indent) in indents {
5207 if indent.len == 0 {
5208 continue;
5209 }
5210
5211 let text = match indent.kind {
5212 IndentKind::Space => " ".repeat(indent.len as usize),
5213 IndentKind::Tab => "\t".repeat(indent.len as usize),
5214 };
5215 let point = Point::new(row.0, 0);
5216 indent_edits.push((point..point, text));
5217 }
5218 }
5219 editor.edit(indent_edits, cx);
5220 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5221 format.detach_and_log_err(cx);
5222 }
5223 });
5224 }
5225
5226 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5227 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5228
5229 let buffer = self.buffer.read(cx);
5230 let snapshot = buffer.snapshot(cx);
5231
5232 let mut edits = Vec::new();
5233 let mut rows = Vec::new();
5234 let mut rows_inserted = 0;
5235
5236 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5237 let cursor = selection.head();
5238 let row = cursor.row;
5239
5240 let point = Point::new(row + 1, 0);
5241 let start_of_line = snapshot.clip_point(point, Bias::Left);
5242
5243 let newline = "\n".to_string();
5244 edits.push((start_of_line..start_of_line, newline));
5245
5246 rows_inserted += 1;
5247 rows.push(row + rows_inserted);
5248 }
5249
5250 self.transact(window, cx, |editor, window, cx| {
5251 editor.edit(edits, cx);
5252
5253 editor.change_selections(Default::default(), window, cx, |s| {
5254 let mut index = 0;
5255 s.move_cursors_with(|map, _, _| {
5256 let row = rows[index];
5257 index += 1;
5258
5259 let point = Point::new(row, 0);
5260 let boundary = map.next_line_boundary(point).1;
5261 let clipped = map.clip_point(boundary, Bias::Left);
5262
5263 (clipped, SelectionGoal::None)
5264 });
5265 });
5266
5267 let mut indent_edits = Vec::new();
5268 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5269 for row in rows {
5270 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5271 for (row, indent) in indents {
5272 if indent.len == 0 {
5273 continue;
5274 }
5275
5276 let text = match indent.kind {
5277 IndentKind::Space => " ".repeat(indent.len as usize),
5278 IndentKind::Tab => "\t".repeat(indent.len as usize),
5279 };
5280 let point = Point::new(row.0, 0);
5281 indent_edits.push((point..point, text));
5282 }
5283 }
5284 editor.edit(indent_edits, cx);
5285 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5286 format.detach_and_log_err(cx);
5287 }
5288 });
5289 }
5290
5291 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5292 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5293 original_indent_columns: Vec::new(),
5294 });
5295 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5296 }
5297
5298 fn insert_with_autoindent_mode(
5299 &mut self,
5300 text: &str,
5301 autoindent_mode: Option<AutoindentMode>,
5302 window: &mut Window,
5303 cx: &mut Context<Self>,
5304 ) {
5305 if self.read_only(cx) {
5306 return;
5307 }
5308
5309 let text: Arc<str> = text.into();
5310 self.transact(window, cx, |this, window, cx| {
5311 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5312 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5313 let anchors = {
5314 let snapshot = buffer.read(cx);
5315 old_selections
5316 .iter()
5317 .map(|s| {
5318 let anchor = snapshot.anchor_after(s.head());
5319 s.map(|_| anchor)
5320 })
5321 .collect::<Vec<_>>()
5322 };
5323 buffer.edit(
5324 old_selections
5325 .iter()
5326 .map(|s| (s.start..s.end, text.clone())),
5327 autoindent_mode,
5328 cx,
5329 );
5330 anchors
5331 });
5332
5333 this.change_selections(Default::default(), window, cx, |s| {
5334 s.select_anchors(selection_anchors);
5335 });
5336
5337 cx.notify();
5338 });
5339 }
5340
5341 fn trigger_completion_on_input(
5342 &mut self,
5343 text: &str,
5344 trigger_in_words: bool,
5345 window: &mut Window,
5346 cx: &mut Context<Self>,
5347 ) {
5348 let completions_source = self
5349 .context_menu
5350 .borrow()
5351 .as_ref()
5352 .and_then(|menu| match menu {
5353 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5354 CodeContextMenu::CodeActions(_) => None,
5355 });
5356
5357 match completions_source {
5358 Some(CompletionsMenuSource::Words { .. }) => {
5359 self.open_or_update_completions_menu(
5360 Some(CompletionsMenuSource::Words {
5361 ignore_threshold: false,
5362 }),
5363 None,
5364 trigger_in_words,
5365 window,
5366 cx,
5367 );
5368 }
5369 _ => self.open_or_update_completions_menu(
5370 None,
5371 Some(text.to_owned()).filter(|x| !x.is_empty()),
5372 true,
5373 window,
5374 cx,
5375 ),
5376 }
5377 }
5378
5379 /// If any empty selections is touching the start of its innermost containing autoclose
5380 /// region, expand it to select the brackets.
5381 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5382 let selections = self
5383 .selections
5384 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5385 let buffer = self.buffer.read(cx).read(cx);
5386 let new_selections = self
5387 .selections_with_autoclose_regions(selections, &buffer)
5388 .map(|(mut selection, region)| {
5389 if !selection.is_empty() {
5390 return selection;
5391 }
5392
5393 if let Some(region) = region {
5394 let mut range = region.range.to_offset(&buffer);
5395 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5396 range.start -= region.pair.start.len();
5397 if buffer.contains_str_at(range.start, ®ion.pair.start)
5398 && buffer.contains_str_at(range.end, ®ion.pair.end)
5399 {
5400 range.end += region.pair.end.len();
5401 selection.start = range.start;
5402 selection.end = range.end;
5403
5404 return selection;
5405 }
5406 }
5407 }
5408
5409 let always_treat_brackets_as_autoclosed = buffer
5410 .language_settings_at(selection.start, cx)
5411 .always_treat_brackets_as_autoclosed;
5412
5413 if !always_treat_brackets_as_autoclosed {
5414 return selection;
5415 }
5416
5417 if let Some(scope) = buffer.language_scope_at(selection.start) {
5418 for (pair, enabled) in scope.brackets() {
5419 if !enabled || !pair.close {
5420 continue;
5421 }
5422
5423 if buffer.contains_str_at(selection.start, &pair.end) {
5424 let pair_start_len = pair.start.len();
5425 if buffer.contains_str_at(
5426 selection.start.saturating_sub_usize(pair_start_len),
5427 &pair.start,
5428 ) {
5429 selection.start -= pair_start_len;
5430 selection.end += pair.end.len();
5431
5432 return selection;
5433 }
5434 }
5435 }
5436 }
5437
5438 selection
5439 })
5440 .collect();
5441
5442 drop(buffer);
5443 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5444 selections.select(new_selections)
5445 });
5446 }
5447
5448 /// Iterate the given selections, and for each one, find the smallest surrounding
5449 /// autoclose region. This uses the ordering of the selections and the autoclose
5450 /// regions to avoid repeated comparisons.
5451 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5452 &'a self,
5453 selections: impl IntoIterator<Item = Selection<D>>,
5454 buffer: &'a MultiBufferSnapshot,
5455 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5456 let mut i = 0;
5457 let mut regions = self.autoclose_regions.as_slice();
5458 selections.into_iter().map(move |selection| {
5459 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5460
5461 let mut enclosing = None;
5462 while let Some(pair_state) = regions.get(i) {
5463 if pair_state.range.end.to_offset(buffer) < range.start {
5464 regions = ®ions[i + 1..];
5465 i = 0;
5466 } else if pair_state.range.start.to_offset(buffer) > range.end {
5467 break;
5468 } else {
5469 if pair_state.selection_id == selection.id {
5470 enclosing = Some(pair_state);
5471 }
5472 i += 1;
5473 }
5474 }
5475
5476 (selection, enclosing)
5477 })
5478 }
5479
5480 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5481 fn invalidate_autoclose_regions(
5482 &mut self,
5483 mut selections: &[Selection<Anchor>],
5484 buffer: &MultiBufferSnapshot,
5485 ) {
5486 self.autoclose_regions.retain(|state| {
5487 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5488 return false;
5489 }
5490
5491 let mut i = 0;
5492 while let Some(selection) = selections.get(i) {
5493 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5494 selections = &selections[1..];
5495 continue;
5496 }
5497 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5498 break;
5499 }
5500 if selection.id == state.selection_id {
5501 return true;
5502 } else {
5503 i += 1;
5504 }
5505 }
5506 false
5507 });
5508 }
5509
5510 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5511 let offset = position.to_offset(buffer);
5512 let (word_range, kind) =
5513 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5514 if offset > word_range.start && kind == Some(CharKind::Word) {
5515 Some(
5516 buffer
5517 .text_for_range(word_range.start..offset)
5518 .collect::<String>(),
5519 )
5520 } else {
5521 None
5522 }
5523 }
5524
5525 pub fn visible_excerpts(
5526 &self,
5527 lsp_related_only: bool,
5528 cx: &mut Context<Editor>,
5529 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5530 let project = self.project().cloned();
5531 let multi_buffer = self.buffer().read(cx);
5532 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5533 let multi_buffer_visible_start = self
5534 .scroll_manager
5535 .anchor()
5536 .anchor
5537 .to_point(&multi_buffer_snapshot);
5538 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5539 multi_buffer_visible_start
5540 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5541 Bias::Left,
5542 );
5543 multi_buffer_snapshot
5544 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5545 .into_iter()
5546 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5547 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5548 if !lsp_related_only {
5549 return Some((
5550 excerpt_id,
5551 (
5552 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5553 buffer.version().clone(),
5554 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5555 ),
5556 ));
5557 }
5558
5559 let project = project.as_ref()?.read(cx);
5560 let buffer_file = project::File::from_dyn(buffer.file())?;
5561 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5562 let worktree_entry = buffer_worktree
5563 .read(cx)
5564 .entry_for_id(buffer_file.project_entry_id()?)?;
5565 if worktree_entry.is_ignored {
5566 None
5567 } else {
5568 Some((
5569 excerpt_id,
5570 (
5571 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5572 buffer.version().clone(),
5573 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5574 ),
5575 ))
5576 }
5577 })
5578 .collect()
5579 }
5580
5581 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5582 TextLayoutDetails {
5583 text_system: window.text_system().clone(),
5584 editor_style: self.style.clone().unwrap(),
5585 rem_size: window.rem_size(),
5586 scroll_anchor: self.scroll_manager.anchor(),
5587 visible_rows: self.visible_line_count(),
5588 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5589 }
5590 }
5591
5592 fn trigger_on_type_formatting(
5593 &self,
5594 input: String,
5595 window: &mut Window,
5596 cx: &mut Context<Self>,
5597 ) -> Option<Task<Result<()>>> {
5598 if input.chars().count() != 1 {
5599 return None;
5600 }
5601
5602 let project = self.project()?;
5603 let position = self.selections.newest_anchor().head();
5604 let (buffer, buffer_position) = self
5605 .buffer
5606 .read(cx)
5607 .text_anchor_for_position(position, cx)?;
5608
5609 let settings = language_settings::language_settings(
5610 buffer
5611 .read(cx)
5612 .language_at(buffer_position)
5613 .map(|l| l.name()),
5614 buffer.read(cx).file(),
5615 cx,
5616 );
5617 if !settings.use_on_type_format {
5618 return None;
5619 }
5620
5621 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5622 // hence we do LSP request & edit on host side only — add formats to host's history.
5623 let push_to_lsp_host_history = true;
5624 // If this is not the host, append its history with new edits.
5625 let push_to_client_history = project.read(cx).is_via_collab();
5626
5627 let on_type_formatting = project.update(cx, |project, cx| {
5628 project.on_type_format(
5629 buffer.clone(),
5630 buffer_position,
5631 input,
5632 push_to_lsp_host_history,
5633 cx,
5634 )
5635 });
5636 Some(cx.spawn_in(window, async move |editor, cx| {
5637 if let Some(transaction) = on_type_formatting.await? {
5638 if push_to_client_history {
5639 buffer.update(cx, |buffer, _| {
5640 buffer.push_transaction(transaction, Instant::now());
5641 buffer.finalize_last_transaction();
5642 });
5643 }
5644 editor.update(cx, |editor, cx| {
5645 editor.refresh_document_highlights(cx);
5646 })?;
5647 }
5648 Ok(())
5649 }))
5650 }
5651
5652 pub fn show_word_completions(
5653 &mut self,
5654 _: &ShowWordCompletions,
5655 window: &mut Window,
5656 cx: &mut Context<Self>,
5657 ) {
5658 self.open_or_update_completions_menu(
5659 Some(CompletionsMenuSource::Words {
5660 ignore_threshold: true,
5661 }),
5662 None,
5663 false,
5664 window,
5665 cx,
5666 );
5667 }
5668
5669 pub fn show_completions(
5670 &mut self,
5671 _: &ShowCompletions,
5672 window: &mut Window,
5673 cx: &mut Context<Self>,
5674 ) {
5675 self.open_or_update_completions_menu(None, None, false, window, cx);
5676 }
5677
5678 fn open_or_update_completions_menu(
5679 &mut self,
5680 requested_source: Option<CompletionsMenuSource>,
5681 trigger: Option<String>,
5682 trigger_in_words: bool,
5683 window: &mut Window,
5684 cx: &mut Context<Self>,
5685 ) {
5686 if self.pending_rename.is_some() {
5687 return;
5688 }
5689
5690 let completions_source = self
5691 .context_menu
5692 .borrow()
5693 .as_ref()
5694 .and_then(|menu| match menu {
5695 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5696 CodeContextMenu::CodeActions(_) => None,
5697 });
5698
5699 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5700
5701 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5702 // inserted and selected. To handle that case, the start of the selection is used so that
5703 // the menu starts with all choices.
5704 let position = self
5705 .selections
5706 .newest_anchor()
5707 .start
5708 .bias_right(&multibuffer_snapshot);
5709 if position.diff_base_anchor.is_some() {
5710 return;
5711 }
5712 let buffer_position = multibuffer_snapshot.anchor_before(position);
5713 let Some(buffer) = buffer_position
5714 .text_anchor
5715 .buffer_id
5716 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5717 else {
5718 return;
5719 };
5720 let buffer_snapshot = buffer.read(cx).snapshot();
5721
5722 let menu_is_open = matches!(
5723 self.context_menu.borrow().as_ref(),
5724 Some(CodeContextMenu::Completions(_))
5725 );
5726
5727 let language = buffer_snapshot
5728 .language_at(buffer_position.text_anchor)
5729 .map(|language| language.name());
5730
5731 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5732 let completion_settings = language_settings.completions.clone();
5733
5734 let show_completions_on_input = self
5735 .show_completions_on_input_override
5736 .unwrap_or(language_settings.show_completions_on_input);
5737 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5738 return;
5739 }
5740
5741 let query: Option<Arc<String>> =
5742 Self::completion_query(&multibuffer_snapshot, buffer_position)
5743 .map(|query| query.into());
5744
5745 drop(multibuffer_snapshot);
5746
5747 // Hide the current completions menu when query is empty. Without this, cached
5748 // completions from before the trigger char may be reused (#32774).
5749 if query.is_none() && menu_is_open {
5750 self.hide_context_menu(window, cx);
5751 }
5752
5753 let mut ignore_word_threshold = false;
5754 let provider = match requested_source {
5755 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5756 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5757 ignore_word_threshold = ignore_threshold;
5758 None
5759 }
5760 Some(CompletionsMenuSource::SnippetChoices)
5761 | Some(CompletionsMenuSource::SnippetsOnly) => {
5762 log::error!("bug: SnippetChoices requested_source is not handled");
5763 None
5764 }
5765 };
5766
5767 let sort_completions = provider
5768 .as_ref()
5769 .is_some_and(|provider| provider.sort_completions());
5770
5771 let filter_completions = provider
5772 .as_ref()
5773 .is_none_or(|provider| provider.filter_completions());
5774
5775 let was_snippets_only = matches!(
5776 completions_source,
5777 Some(CompletionsMenuSource::SnippetsOnly)
5778 );
5779
5780 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5781 if filter_completions {
5782 menu.filter(
5783 query.clone().unwrap_or_default(),
5784 buffer_position.text_anchor,
5785 &buffer,
5786 provider.clone(),
5787 window,
5788 cx,
5789 );
5790 }
5791 // When `is_incomplete` is false, no need to re-query completions when the current query
5792 // is a suffix of the initial query.
5793 let was_complete = !menu.is_incomplete;
5794 if was_complete && !was_snippets_only {
5795 // If the new query is a suffix of the old query (typing more characters) and
5796 // the previous result was complete, the existing completions can be filtered.
5797 //
5798 // Note that snippet completions are always complete.
5799 let query_matches = match (&menu.initial_query, &query) {
5800 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5801 (None, _) => true,
5802 _ => false,
5803 };
5804 if query_matches {
5805 let position_matches = if menu.initial_position == position {
5806 true
5807 } else {
5808 let snapshot = self.buffer.read(cx).read(cx);
5809 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5810 };
5811 if position_matches {
5812 return;
5813 }
5814 }
5815 }
5816 };
5817
5818 let Anchor {
5819 excerpt_id: buffer_excerpt_id,
5820 text_anchor: buffer_position,
5821 ..
5822 } = buffer_position;
5823
5824 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5825 buffer_snapshot.surrounding_word(buffer_position, None)
5826 {
5827 let word_to_exclude = buffer_snapshot
5828 .text_for_range(word_range.clone())
5829 .collect::<String>();
5830 (
5831 buffer_snapshot.anchor_before(word_range.start)
5832 ..buffer_snapshot.anchor_after(buffer_position),
5833 Some(word_to_exclude),
5834 )
5835 } else {
5836 (buffer_position..buffer_position, None)
5837 };
5838
5839 let show_completion_documentation = buffer_snapshot
5840 .settings_at(buffer_position, cx)
5841 .show_completion_documentation;
5842
5843 // The document can be large, so stay in reasonable bounds when searching for words,
5844 // otherwise completion pop-up might be slow to appear.
5845 const WORD_LOOKUP_ROWS: u32 = 5_000;
5846 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5847 let min_word_search = buffer_snapshot.clip_point(
5848 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5849 Bias::Left,
5850 );
5851 let max_word_search = buffer_snapshot.clip_point(
5852 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5853 Bias::Right,
5854 );
5855 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5856 ..buffer_snapshot.point_to_offset(max_word_search);
5857
5858 let skip_digits = query
5859 .as_ref()
5860 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5861
5862 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5863 trigger.as_ref().is_none_or(|trigger| {
5864 provider.is_completion_trigger(
5865 &buffer,
5866 position.text_anchor,
5867 trigger,
5868 trigger_in_words,
5869 cx,
5870 )
5871 })
5872 });
5873
5874 let provider_responses = if let Some(provider) = &provider
5875 && load_provider_completions
5876 {
5877 let trigger_character =
5878 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5879 let completion_context = CompletionContext {
5880 trigger_kind: match &trigger_character {
5881 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5882 None => CompletionTriggerKind::INVOKED,
5883 },
5884 trigger_character,
5885 };
5886
5887 provider.completions(
5888 buffer_excerpt_id,
5889 &buffer,
5890 buffer_position,
5891 completion_context,
5892 window,
5893 cx,
5894 )
5895 } else {
5896 Task::ready(Ok(Vec::new()))
5897 };
5898
5899 let load_word_completions = if !self.word_completions_enabled {
5900 false
5901 } else if requested_source
5902 == Some(CompletionsMenuSource::Words {
5903 ignore_threshold: true,
5904 })
5905 {
5906 true
5907 } else {
5908 load_provider_completions
5909 && completion_settings.words != WordsCompletionMode::Disabled
5910 && (ignore_word_threshold || {
5911 let words_min_length = completion_settings.words_min_length;
5912 // check whether word has at least `words_min_length` characters
5913 let query_chars = query.iter().flat_map(|q| q.chars());
5914 query_chars.take(words_min_length).count() == words_min_length
5915 })
5916 };
5917
5918 let mut words = if load_word_completions {
5919 cx.background_spawn({
5920 let buffer_snapshot = buffer_snapshot.clone();
5921 async move {
5922 buffer_snapshot.words_in_range(WordsQuery {
5923 fuzzy_contents: None,
5924 range: word_search_range,
5925 skip_digits,
5926 })
5927 }
5928 })
5929 } else {
5930 Task::ready(BTreeMap::default())
5931 };
5932
5933 let snippets = if let Some(provider) = &provider
5934 && provider.show_snippets()
5935 && let Some(project) = self.project()
5936 {
5937 let char_classifier = buffer_snapshot
5938 .char_classifier_at(buffer_position)
5939 .scope_context(Some(CharScopeContext::Completion));
5940 project.update(cx, |project, cx| {
5941 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5942 })
5943 } else {
5944 Task::ready(Ok(CompletionResponse {
5945 completions: Vec::new(),
5946 display_options: Default::default(),
5947 is_incomplete: false,
5948 }))
5949 };
5950
5951 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5952
5953 let id = post_inc(&mut self.next_completion_id);
5954 let task = cx.spawn_in(window, async move |editor, cx| {
5955 let Ok(()) = editor.update(cx, |this, _| {
5956 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5957 }) else {
5958 return;
5959 };
5960
5961 // TODO: Ideally completions from different sources would be selectively re-queried, so
5962 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5963 let mut completions = Vec::new();
5964 let mut is_incomplete = false;
5965 let mut display_options: Option<CompletionDisplayOptions> = None;
5966 if let Some(provider_responses) = provider_responses.await.log_err()
5967 && !provider_responses.is_empty()
5968 {
5969 for response in provider_responses {
5970 completions.extend(response.completions);
5971 is_incomplete = is_incomplete || response.is_incomplete;
5972 match display_options.as_mut() {
5973 None => {
5974 display_options = Some(response.display_options);
5975 }
5976 Some(options) => options.merge(&response.display_options),
5977 }
5978 }
5979 if completion_settings.words == WordsCompletionMode::Fallback {
5980 words = Task::ready(BTreeMap::default());
5981 }
5982 }
5983 let display_options = display_options.unwrap_or_default();
5984
5985 let mut words = words.await;
5986 if let Some(word_to_exclude) = &word_to_exclude {
5987 words.remove(word_to_exclude);
5988 }
5989 for lsp_completion in &completions {
5990 words.remove(&lsp_completion.new_text);
5991 }
5992 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5993 replace_range: word_replace_range.clone(),
5994 new_text: word.clone(),
5995 label: CodeLabel::plain(word, None),
5996 match_start: None,
5997 snippet_deduplication_key: None,
5998 icon_path: None,
5999 documentation: None,
6000 source: CompletionSource::BufferWord {
6001 word_range,
6002 resolved: false,
6003 },
6004 insert_text_mode: Some(InsertTextMode::AS_IS),
6005 confirm: None,
6006 }));
6007
6008 completions.extend(
6009 snippets
6010 .await
6011 .into_iter()
6012 .flat_map(|response| response.completions),
6013 );
6014
6015 let menu = if completions.is_empty() {
6016 None
6017 } else {
6018 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6019 let languages = editor
6020 .workspace
6021 .as_ref()
6022 .and_then(|(workspace, _)| workspace.upgrade())
6023 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6024 let menu = CompletionsMenu::new(
6025 id,
6026 requested_source.unwrap_or(if load_provider_completions {
6027 CompletionsMenuSource::Normal
6028 } else {
6029 CompletionsMenuSource::SnippetsOnly
6030 }),
6031 sort_completions,
6032 show_completion_documentation,
6033 position,
6034 query.clone(),
6035 is_incomplete,
6036 buffer.clone(),
6037 completions.into(),
6038 editor
6039 .context_menu()
6040 .borrow_mut()
6041 .as_ref()
6042 .map(|menu| menu.primary_scroll_handle()),
6043 display_options,
6044 snippet_sort_order,
6045 languages,
6046 language,
6047 cx,
6048 );
6049
6050 let query = if filter_completions { query } else { None };
6051 let matches_task = menu.do_async_filtering(
6052 query.unwrap_or_default(),
6053 buffer_position,
6054 &buffer,
6055 cx,
6056 );
6057 (menu, matches_task)
6058 }) else {
6059 return;
6060 };
6061
6062 let matches = matches_task.await;
6063
6064 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6065 // Newer menu already set, so exit.
6066 if let Some(CodeContextMenu::Completions(prev_menu)) =
6067 editor.context_menu.borrow().as_ref()
6068 && prev_menu.id > id
6069 {
6070 return;
6071 };
6072
6073 // Only valid to take prev_menu because either the new menu is immediately set
6074 // below, or the menu is hidden.
6075 if let Some(CodeContextMenu::Completions(prev_menu)) =
6076 editor.context_menu.borrow_mut().take()
6077 {
6078 let position_matches =
6079 if prev_menu.initial_position == menu.initial_position {
6080 true
6081 } else {
6082 let snapshot = editor.buffer.read(cx).read(cx);
6083 prev_menu.initial_position.to_offset(&snapshot)
6084 == menu.initial_position.to_offset(&snapshot)
6085 };
6086 if position_matches {
6087 // Preserve markdown cache before `set_filter_results` because it will
6088 // try to populate the documentation cache.
6089 menu.preserve_markdown_cache(prev_menu);
6090 }
6091 };
6092
6093 menu.set_filter_results(matches, provider, window, cx);
6094 }) else {
6095 return;
6096 };
6097
6098 menu.visible().then_some(menu)
6099 };
6100
6101 editor
6102 .update_in(cx, |editor, window, cx| {
6103 if editor.focus_handle.is_focused(window)
6104 && let Some(menu) = menu
6105 {
6106 *editor.context_menu.borrow_mut() =
6107 Some(CodeContextMenu::Completions(menu));
6108
6109 crate::hover_popover::hide_hover(editor, cx);
6110 if editor.show_edit_predictions_in_menu() {
6111 editor.update_visible_edit_prediction(window, cx);
6112 } else {
6113 editor.discard_edit_prediction(false, cx);
6114 }
6115
6116 cx.notify();
6117 return;
6118 }
6119
6120 if editor.completion_tasks.len() <= 1 {
6121 // If there are no more completion tasks and the last menu was empty, we should hide it.
6122 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6123 // If it was already hidden and we don't show edit predictions in the menu,
6124 // we should also show the edit prediction when available.
6125 if was_hidden && editor.show_edit_predictions_in_menu() {
6126 editor.update_visible_edit_prediction(window, cx);
6127 }
6128 }
6129 })
6130 .ok();
6131 });
6132
6133 self.completion_tasks.push((id, task));
6134 }
6135
6136 #[cfg(feature = "test-support")]
6137 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6138 let menu = self.context_menu.borrow();
6139 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6140 let completions = menu.completions.borrow();
6141 Some(completions.to_vec())
6142 } else {
6143 None
6144 }
6145 }
6146
6147 pub fn with_completions_menu_matching_id<R>(
6148 &self,
6149 id: CompletionId,
6150 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6151 ) -> R {
6152 let mut context_menu = self.context_menu.borrow_mut();
6153 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6154 return f(None);
6155 };
6156 if completions_menu.id != id {
6157 return f(None);
6158 }
6159 f(Some(completions_menu))
6160 }
6161
6162 pub fn confirm_completion(
6163 &mut self,
6164 action: &ConfirmCompletion,
6165 window: &mut Window,
6166 cx: &mut Context<Self>,
6167 ) -> Option<Task<Result<()>>> {
6168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6169 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6170 }
6171
6172 pub fn confirm_completion_insert(
6173 &mut self,
6174 _: &ConfirmCompletionInsert,
6175 window: &mut Window,
6176 cx: &mut Context<Self>,
6177 ) -> Option<Task<Result<()>>> {
6178 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6179 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6180 }
6181
6182 pub fn confirm_completion_replace(
6183 &mut self,
6184 _: &ConfirmCompletionReplace,
6185 window: &mut Window,
6186 cx: &mut Context<Self>,
6187 ) -> Option<Task<Result<()>>> {
6188 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6189 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6190 }
6191
6192 pub fn compose_completion(
6193 &mut self,
6194 action: &ComposeCompletion,
6195 window: &mut Window,
6196 cx: &mut Context<Self>,
6197 ) -> Option<Task<Result<()>>> {
6198 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6199 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6200 }
6201
6202 fn do_completion(
6203 &mut self,
6204 item_ix: Option<usize>,
6205 intent: CompletionIntent,
6206 window: &mut Window,
6207 cx: &mut Context<Editor>,
6208 ) -> Option<Task<Result<()>>> {
6209 use language::ToOffset as _;
6210
6211 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6212 else {
6213 return None;
6214 };
6215
6216 let candidate_id = {
6217 let entries = completions_menu.entries.borrow();
6218 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6219 if self.show_edit_predictions_in_menu() {
6220 self.discard_edit_prediction(true, cx);
6221 }
6222 mat.candidate_id
6223 };
6224
6225 let completion = completions_menu
6226 .completions
6227 .borrow()
6228 .get(candidate_id)?
6229 .clone();
6230 cx.stop_propagation();
6231
6232 let buffer_handle = completions_menu.buffer.clone();
6233
6234 let CompletionEdit {
6235 new_text,
6236 snippet,
6237 replace_range,
6238 } = process_completion_for_edit(
6239 &completion,
6240 intent,
6241 &buffer_handle,
6242 &completions_menu.initial_position.text_anchor,
6243 cx,
6244 );
6245
6246 let buffer = buffer_handle.read(cx);
6247 let snapshot = self.buffer.read(cx).snapshot(cx);
6248 let newest_anchor = self.selections.newest_anchor();
6249 let replace_range_multibuffer = {
6250 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6251 excerpt.map_range_from_buffer(replace_range.clone())
6252 };
6253 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6254 return None;
6255 }
6256
6257 let old_text = buffer
6258 .text_for_range(replace_range.clone())
6259 .collect::<String>();
6260 let lookbehind = newest_anchor
6261 .start
6262 .text_anchor
6263 .to_offset(buffer)
6264 .saturating_sub(replace_range.start.0);
6265 let lookahead = replace_range
6266 .end
6267 .0
6268 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6269 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6270 let suffix = &old_text[lookbehind.min(old_text.len())..];
6271
6272 let selections = self
6273 .selections
6274 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6275 let mut ranges = Vec::new();
6276 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6277
6278 for selection in &selections {
6279 let range = if selection.id == newest_anchor.id {
6280 replace_range_multibuffer.clone()
6281 } else {
6282 let mut range = selection.range();
6283
6284 // if prefix is present, don't duplicate it
6285 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6286 range.start = range.start.saturating_sub_usize(lookbehind);
6287
6288 // if suffix is also present, mimic the newest cursor and replace it
6289 if selection.id != newest_anchor.id
6290 && snapshot.contains_str_at(range.end, suffix)
6291 {
6292 range.end += lookahead;
6293 }
6294 }
6295 range
6296 };
6297
6298 ranges.push(range.clone());
6299
6300 if !self.linked_edit_ranges.is_empty() {
6301 let start_anchor = snapshot.anchor_before(range.start);
6302 let end_anchor = snapshot.anchor_after(range.end);
6303 if let Some(ranges) = self
6304 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6305 {
6306 for (buffer, edits) in ranges {
6307 linked_edits
6308 .entry(buffer.clone())
6309 .or_default()
6310 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6311 }
6312 }
6313 }
6314 }
6315
6316 let common_prefix_len = old_text
6317 .chars()
6318 .zip(new_text.chars())
6319 .take_while(|(a, b)| a == b)
6320 .map(|(a, _)| a.len_utf8())
6321 .sum::<usize>();
6322
6323 cx.emit(EditorEvent::InputHandled {
6324 utf16_range_to_replace: None,
6325 text: new_text[common_prefix_len..].into(),
6326 });
6327
6328 self.transact(window, cx, |editor, window, cx| {
6329 if let Some(mut snippet) = snippet {
6330 snippet.text = new_text.to_string();
6331 editor
6332 .insert_snippet(&ranges, snippet, window, cx)
6333 .log_err();
6334 } else {
6335 editor.buffer.update(cx, |multi_buffer, cx| {
6336 let auto_indent = match completion.insert_text_mode {
6337 Some(InsertTextMode::AS_IS) => None,
6338 _ => editor.autoindent_mode.clone(),
6339 };
6340 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6341 multi_buffer.edit(edits, auto_indent, cx);
6342 });
6343 }
6344 for (buffer, edits) in linked_edits {
6345 buffer.update(cx, |buffer, cx| {
6346 let snapshot = buffer.snapshot();
6347 let edits = edits
6348 .into_iter()
6349 .map(|(range, text)| {
6350 use text::ToPoint as TP;
6351 let end_point = TP::to_point(&range.end, &snapshot);
6352 let start_point = TP::to_point(&range.start, &snapshot);
6353 (start_point..end_point, text)
6354 })
6355 .sorted_by_key(|(range, _)| range.start);
6356 buffer.edit(edits, None, cx);
6357 })
6358 }
6359
6360 editor.refresh_edit_prediction(true, false, window, cx);
6361 });
6362 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6363
6364 let show_new_completions_on_confirm = completion
6365 .confirm
6366 .as_ref()
6367 .is_some_and(|confirm| confirm(intent, window, cx));
6368 if show_new_completions_on_confirm {
6369 self.open_or_update_completions_menu(None, None, false, window, cx);
6370 }
6371
6372 let provider = self.completion_provider.as_ref()?;
6373
6374 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6375 let command = lsp_store.as_ref().and_then(|lsp_store| {
6376 let CompletionSource::Lsp {
6377 lsp_completion,
6378 server_id,
6379 ..
6380 } = &completion.source
6381 else {
6382 return None;
6383 };
6384 let lsp_command = lsp_completion.command.as_ref()?;
6385 let available_commands = lsp_store
6386 .read(cx)
6387 .lsp_server_capabilities
6388 .get(server_id)
6389 .and_then(|server_capabilities| {
6390 server_capabilities
6391 .execute_command_provider
6392 .as_ref()
6393 .map(|options| options.commands.as_slice())
6394 })?;
6395 if available_commands.contains(&lsp_command.command) {
6396 Some(CodeAction {
6397 server_id: *server_id,
6398 range: language::Anchor::MIN..language::Anchor::MIN,
6399 lsp_action: LspAction::Command(lsp_command.clone()),
6400 resolved: false,
6401 })
6402 } else {
6403 None
6404 }
6405 });
6406
6407 drop(completion);
6408 let apply_edits = provider.apply_additional_edits_for_completion(
6409 buffer_handle.clone(),
6410 completions_menu.completions.clone(),
6411 candidate_id,
6412 true,
6413 cx,
6414 );
6415
6416 let editor_settings = EditorSettings::get_global(cx);
6417 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6418 // After the code completion is finished, users often want to know what signatures are needed.
6419 // so we should automatically call signature_help
6420 self.show_signature_help(&ShowSignatureHelp, window, cx);
6421 }
6422
6423 Some(cx.spawn_in(window, async move |editor, cx| {
6424 apply_edits.await?;
6425
6426 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6427 let title = command.lsp_action.title().to_owned();
6428 let project_transaction = lsp_store
6429 .update(cx, |lsp_store, cx| {
6430 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6431 })
6432 .await
6433 .context("applying post-completion command")?;
6434 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6435 Self::open_project_transaction(
6436 &editor,
6437 workspace.downgrade(),
6438 project_transaction,
6439 title,
6440 cx,
6441 )
6442 .await?;
6443 }
6444 }
6445
6446 Ok(())
6447 }))
6448 }
6449
6450 pub fn toggle_code_actions(
6451 &mut self,
6452 action: &ToggleCodeActions,
6453 window: &mut Window,
6454 cx: &mut Context<Self>,
6455 ) {
6456 let quick_launch = action.quick_launch;
6457 let mut context_menu = self.context_menu.borrow_mut();
6458 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6459 if code_actions.deployed_from == action.deployed_from {
6460 // Toggle if we're selecting the same one
6461 *context_menu = None;
6462 cx.notify();
6463 return;
6464 } else {
6465 // Otherwise, clear it and start a new one
6466 *context_menu = None;
6467 cx.notify();
6468 }
6469 }
6470 drop(context_menu);
6471 let snapshot = self.snapshot(window, cx);
6472 let deployed_from = action.deployed_from.clone();
6473 let action = action.clone();
6474 self.completion_tasks.clear();
6475 self.discard_edit_prediction(false, cx);
6476
6477 let multibuffer_point = match &action.deployed_from {
6478 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6479 DisplayPoint::new(*row, 0).to_point(&snapshot)
6480 }
6481 _ => self
6482 .selections
6483 .newest::<Point>(&snapshot.display_snapshot)
6484 .head(),
6485 };
6486 let Some((buffer, buffer_row)) = snapshot
6487 .buffer_snapshot()
6488 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6489 .and_then(|(buffer_snapshot, range)| {
6490 self.buffer()
6491 .read(cx)
6492 .buffer(buffer_snapshot.remote_id())
6493 .map(|buffer| (buffer, range.start.row))
6494 })
6495 else {
6496 return;
6497 };
6498 let buffer_id = buffer.read(cx).remote_id();
6499 let tasks = self
6500 .tasks
6501 .get(&(buffer_id, buffer_row))
6502 .map(|t| Arc::new(t.to_owned()));
6503
6504 if !self.focus_handle.is_focused(window) {
6505 return;
6506 }
6507 let project = self.project.clone();
6508
6509 let code_actions_task = match deployed_from {
6510 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6511 _ => self.code_actions(buffer_row, window, cx),
6512 };
6513
6514 let runnable_task = match deployed_from {
6515 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6516 _ => {
6517 let mut task_context_task = Task::ready(None);
6518 if let Some(tasks) = &tasks
6519 && let Some(project) = project
6520 {
6521 task_context_task =
6522 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6523 }
6524
6525 cx.spawn_in(window, {
6526 let buffer = buffer.clone();
6527 async move |editor, cx| {
6528 let task_context = task_context_task.await;
6529
6530 let resolved_tasks =
6531 tasks
6532 .zip(task_context.clone())
6533 .map(|(tasks, task_context)| ResolvedTasks {
6534 templates: tasks.resolve(&task_context).collect(),
6535 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6536 multibuffer_point.row,
6537 tasks.column,
6538 )),
6539 });
6540 let debug_scenarios = editor
6541 .update(cx, |editor, cx| {
6542 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6543 })?
6544 .await;
6545 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6546 }
6547 })
6548 }
6549 };
6550
6551 cx.spawn_in(window, async move |editor, cx| {
6552 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6553 let code_actions = code_actions_task.await;
6554 let spawn_straight_away = quick_launch
6555 && resolved_tasks
6556 .as_ref()
6557 .is_some_and(|tasks| tasks.templates.len() == 1)
6558 && code_actions
6559 .as_ref()
6560 .is_none_or(|actions| actions.is_empty())
6561 && debug_scenarios.is_empty();
6562
6563 editor.update_in(cx, |editor, window, cx| {
6564 crate::hover_popover::hide_hover(editor, cx);
6565 let actions = CodeActionContents::new(
6566 resolved_tasks,
6567 code_actions,
6568 debug_scenarios,
6569 task_context.unwrap_or_default(),
6570 );
6571
6572 // Don't show the menu if there are no actions available
6573 if actions.is_empty() {
6574 cx.notify();
6575 return Task::ready(Ok(()));
6576 }
6577
6578 *editor.context_menu.borrow_mut() =
6579 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6580 buffer,
6581 actions,
6582 selected_item: Default::default(),
6583 scroll_handle: UniformListScrollHandle::default(),
6584 deployed_from,
6585 }));
6586 cx.notify();
6587 if spawn_straight_away
6588 && let Some(task) = editor.confirm_code_action(
6589 &ConfirmCodeAction { item_ix: Some(0) },
6590 window,
6591 cx,
6592 )
6593 {
6594 return task;
6595 }
6596
6597 Task::ready(Ok(()))
6598 })
6599 })
6600 .detach_and_log_err(cx);
6601 }
6602
6603 fn debug_scenarios(
6604 &mut self,
6605 resolved_tasks: &Option<ResolvedTasks>,
6606 buffer: &Entity<Buffer>,
6607 cx: &mut App,
6608 ) -> Task<Vec<task::DebugScenario>> {
6609 maybe!({
6610 let project = self.project()?;
6611 let dap_store = project.read(cx).dap_store();
6612 let mut scenarios = vec![];
6613 let resolved_tasks = resolved_tasks.as_ref()?;
6614 let buffer = buffer.read(cx);
6615 let language = buffer.language()?;
6616 let file = buffer.file();
6617 let debug_adapter = language_settings(language.name().into(), file, cx)
6618 .debuggers
6619 .first()
6620 .map(SharedString::from)
6621 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6622
6623 dap_store.update(cx, |dap_store, cx| {
6624 for (_, task) in &resolved_tasks.templates {
6625 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6626 task.original_task().clone(),
6627 debug_adapter.clone().into(),
6628 task.display_label().to_owned().into(),
6629 cx,
6630 );
6631 scenarios.push(maybe_scenario);
6632 }
6633 });
6634 Some(cx.background_spawn(async move {
6635 futures::future::join_all(scenarios)
6636 .await
6637 .into_iter()
6638 .flatten()
6639 .collect::<Vec<_>>()
6640 }))
6641 })
6642 .unwrap_or_else(|| Task::ready(vec![]))
6643 }
6644
6645 fn code_actions(
6646 &mut self,
6647 buffer_row: u32,
6648 window: &mut Window,
6649 cx: &mut Context<Self>,
6650 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6651 let mut task = self.code_actions_task.take();
6652 cx.spawn_in(window, async move |editor, cx| {
6653 while let Some(prev_task) = task {
6654 prev_task.await.log_err();
6655 task = editor
6656 .update(cx, |this, _| this.code_actions_task.take())
6657 .ok()?;
6658 }
6659
6660 editor
6661 .update(cx, |editor, cx| {
6662 editor
6663 .available_code_actions
6664 .clone()
6665 .and_then(|(location, code_actions)| {
6666 let snapshot = location.buffer.read(cx).snapshot();
6667 let point_range = location.range.to_point(&snapshot);
6668 let point_range = point_range.start.row..=point_range.end.row;
6669 if point_range.contains(&buffer_row) {
6670 Some(code_actions)
6671 } else {
6672 None
6673 }
6674 })
6675 })
6676 .ok()
6677 .flatten()
6678 })
6679 }
6680
6681 pub fn confirm_code_action(
6682 &mut self,
6683 action: &ConfirmCodeAction,
6684 window: &mut Window,
6685 cx: &mut Context<Self>,
6686 ) -> Option<Task<Result<()>>> {
6687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6688
6689 let actions_menu =
6690 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6691 menu
6692 } else {
6693 return None;
6694 };
6695
6696 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6697 let action = actions_menu.actions.get(action_ix)?;
6698 let title = action.label();
6699 let buffer = actions_menu.buffer;
6700 let workspace = self.workspace()?;
6701
6702 match action {
6703 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6704 workspace.update(cx, |workspace, cx| {
6705 workspace.schedule_resolved_task(
6706 task_source_kind,
6707 resolved_task,
6708 false,
6709 window,
6710 cx,
6711 );
6712
6713 Some(Task::ready(Ok(())))
6714 })
6715 }
6716 CodeActionsItem::CodeAction {
6717 excerpt_id,
6718 action,
6719 provider,
6720 } => {
6721 let apply_code_action =
6722 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6723 let workspace = workspace.downgrade();
6724 Some(cx.spawn_in(window, async move |editor, cx| {
6725 let project_transaction = apply_code_action.await?;
6726 Self::open_project_transaction(
6727 &editor,
6728 workspace,
6729 project_transaction,
6730 title,
6731 cx,
6732 )
6733 .await
6734 }))
6735 }
6736 CodeActionsItem::DebugScenario(scenario) => {
6737 let context = actions_menu.actions.context;
6738
6739 workspace.update(cx, |workspace, cx| {
6740 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6741 workspace.start_debug_session(
6742 scenario,
6743 context,
6744 Some(buffer),
6745 None,
6746 window,
6747 cx,
6748 );
6749 });
6750 Some(Task::ready(Ok(())))
6751 }
6752 }
6753 }
6754
6755 fn open_transaction_for_hidden_buffers(
6756 workspace: Entity<Workspace>,
6757 transaction: ProjectTransaction,
6758 title: String,
6759 window: &mut Window,
6760 cx: &mut Context<Self>,
6761 ) {
6762 if transaction.0.is_empty() {
6763 return;
6764 }
6765
6766 let edited_buffers_already_open = {
6767 let other_editors: Vec<Entity<Editor>> = workspace
6768 .read(cx)
6769 .panes()
6770 .iter()
6771 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6772 .filter(|editor| editor.entity_id() != cx.entity_id())
6773 .collect();
6774
6775 transaction.0.keys().all(|buffer| {
6776 other_editors.iter().any(|editor| {
6777 let multi_buffer = editor.read(cx).buffer();
6778 multi_buffer.read(cx).is_singleton()
6779 && multi_buffer
6780 .read(cx)
6781 .as_singleton()
6782 .map_or(false, |singleton| {
6783 singleton.entity_id() == buffer.entity_id()
6784 })
6785 })
6786 })
6787 };
6788 if !edited_buffers_already_open {
6789 let workspace = workspace.downgrade();
6790 cx.defer_in(window, move |_, window, cx| {
6791 cx.spawn_in(window, async move |editor, cx| {
6792 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6793 .await
6794 .ok()
6795 })
6796 .detach();
6797 });
6798 }
6799 }
6800
6801 pub async fn open_project_transaction(
6802 editor: &WeakEntity<Editor>,
6803 workspace: WeakEntity<Workspace>,
6804 transaction: ProjectTransaction,
6805 title: String,
6806 cx: &mut AsyncWindowContext,
6807 ) -> Result<()> {
6808 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6809 cx.update(|_, cx| {
6810 entries.sort_unstable_by_key(|(buffer, _)| {
6811 buffer.read(cx).file().map(|f| f.path().clone())
6812 });
6813 })?;
6814 if entries.is_empty() {
6815 return Ok(());
6816 }
6817
6818 // If the project transaction's edits are all contained within this editor, then
6819 // avoid opening a new editor to display them.
6820
6821 if let [(buffer, transaction)] = &*entries {
6822 let excerpt = editor.update(cx, |editor, cx| {
6823 editor
6824 .buffer()
6825 .read(cx)
6826 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6827 })?;
6828 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6829 && excerpted_buffer == *buffer
6830 {
6831 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6832 let excerpt_range = excerpt_range.to_offset(buffer);
6833 buffer
6834 .edited_ranges_for_transaction::<usize>(transaction)
6835 .all(|range| {
6836 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6837 })
6838 });
6839
6840 if all_edits_within_excerpt {
6841 return Ok(());
6842 }
6843 }
6844 }
6845
6846 let mut ranges_to_highlight = Vec::new();
6847 let excerpt_buffer = cx.new(|cx| {
6848 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6849 for (buffer_handle, transaction) in &entries {
6850 let edited_ranges = buffer_handle
6851 .read(cx)
6852 .edited_ranges_for_transaction::<Point>(transaction)
6853 .collect::<Vec<_>>();
6854 let (ranges, _) = multibuffer.set_excerpts_for_path(
6855 PathKey::for_buffer(buffer_handle, cx),
6856 buffer_handle.clone(),
6857 edited_ranges,
6858 multibuffer_context_lines(cx),
6859 cx,
6860 );
6861
6862 ranges_to_highlight.extend(ranges);
6863 }
6864 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6865 multibuffer
6866 });
6867
6868 workspace.update_in(cx, |workspace, window, cx| {
6869 let project = workspace.project().clone();
6870 let editor =
6871 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6872 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6873 editor.update(cx, |editor, cx| {
6874 editor.highlight_background::<Self>(
6875 &ranges_to_highlight,
6876 |_, theme| theme.colors().editor_highlighted_line_background,
6877 cx,
6878 );
6879 });
6880 })?;
6881
6882 Ok(())
6883 }
6884
6885 pub fn clear_code_action_providers(&mut self) {
6886 self.code_action_providers.clear();
6887 self.available_code_actions.take();
6888 }
6889
6890 pub fn add_code_action_provider(
6891 &mut self,
6892 provider: Rc<dyn CodeActionProvider>,
6893 window: &mut Window,
6894 cx: &mut Context<Self>,
6895 ) {
6896 if self
6897 .code_action_providers
6898 .iter()
6899 .any(|existing_provider| existing_provider.id() == provider.id())
6900 {
6901 return;
6902 }
6903
6904 self.code_action_providers.push(provider);
6905 self.refresh_code_actions(window, cx);
6906 }
6907
6908 pub fn remove_code_action_provider(
6909 &mut self,
6910 id: Arc<str>,
6911 window: &mut Window,
6912 cx: &mut Context<Self>,
6913 ) {
6914 self.code_action_providers
6915 .retain(|provider| provider.id() != id);
6916 self.refresh_code_actions(window, cx);
6917 }
6918
6919 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6920 !self.code_action_providers.is_empty()
6921 && EditorSettings::get_global(cx).toolbar.code_actions
6922 }
6923
6924 pub fn has_available_code_actions(&self) -> bool {
6925 self.available_code_actions
6926 .as_ref()
6927 .is_some_and(|(_, actions)| !actions.is_empty())
6928 }
6929
6930 fn render_inline_code_actions(
6931 &self,
6932 icon_size: ui::IconSize,
6933 display_row: DisplayRow,
6934 is_active: bool,
6935 cx: &mut Context<Self>,
6936 ) -> AnyElement {
6937 let show_tooltip = !self.context_menu_visible();
6938 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6939 .icon_size(icon_size)
6940 .shape(ui::IconButtonShape::Square)
6941 .icon_color(ui::Color::Hidden)
6942 .toggle_state(is_active)
6943 .when(show_tooltip, |this| {
6944 this.tooltip({
6945 let focus_handle = self.focus_handle.clone();
6946 move |_window, cx| {
6947 Tooltip::for_action_in(
6948 "Toggle Code Actions",
6949 &ToggleCodeActions {
6950 deployed_from: None,
6951 quick_launch: false,
6952 },
6953 &focus_handle,
6954 cx,
6955 )
6956 }
6957 })
6958 })
6959 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6960 window.focus(&editor.focus_handle(cx), cx);
6961 editor.toggle_code_actions(
6962 &crate::actions::ToggleCodeActions {
6963 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6964 display_row,
6965 )),
6966 quick_launch: false,
6967 },
6968 window,
6969 cx,
6970 );
6971 }))
6972 .into_any_element()
6973 }
6974
6975 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6976 &self.context_menu
6977 }
6978
6979 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6980 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6981 cx.background_executor()
6982 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6983 .await;
6984
6985 let (start_buffer, start, _, end, newest_selection) = this
6986 .update(cx, |this, cx| {
6987 let newest_selection = this.selections.newest_anchor().clone();
6988 if newest_selection.head().diff_base_anchor.is_some() {
6989 return None;
6990 }
6991 let display_snapshot = this.display_snapshot(cx);
6992 let newest_selection_adjusted =
6993 this.selections.newest_adjusted(&display_snapshot);
6994 let buffer = this.buffer.read(cx);
6995
6996 let (start_buffer, start) =
6997 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6998 let (end_buffer, end) =
6999 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7000
7001 Some((start_buffer, start, end_buffer, end, newest_selection))
7002 })?
7003 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7004 .context(
7005 "Expected selection to lie in a single buffer when refreshing code actions",
7006 )?;
7007 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7008 let providers = this.code_action_providers.clone();
7009 let tasks = this
7010 .code_action_providers
7011 .iter()
7012 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7013 .collect::<Vec<_>>();
7014 (providers, tasks)
7015 })?;
7016
7017 let mut actions = Vec::new();
7018 for (provider, provider_actions) in
7019 providers.into_iter().zip(future::join_all(tasks).await)
7020 {
7021 if let Some(provider_actions) = provider_actions.log_err() {
7022 actions.extend(provider_actions.into_iter().map(|action| {
7023 AvailableCodeAction {
7024 excerpt_id: newest_selection.start.excerpt_id,
7025 action,
7026 provider: provider.clone(),
7027 }
7028 }));
7029 }
7030 }
7031
7032 this.update(cx, |this, cx| {
7033 this.available_code_actions = if actions.is_empty() {
7034 None
7035 } else {
7036 Some((
7037 Location {
7038 buffer: start_buffer,
7039 range: start..end,
7040 },
7041 actions.into(),
7042 ))
7043 };
7044 cx.notify();
7045 })
7046 }));
7047 }
7048
7049 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7050 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7051 self.show_git_blame_inline = false;
7052
7053 self.show_git_blame_inline_delay_task =
7054 Some(cx.spawn_in(window, async move |this, cx| {
7055 cx.background_executor().timer(delay).await;
7056
7057 this.update(cx, |this, cx| {
7058 this.show_git_blame_inline = true;
7059 cx.notify();
7060 })
7061 .log_err();
7062 }));
7063 }
7064 }
7065
7066 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7067 let snapshot = self.snapshot(window, cx);
7068 let cursor = self
7069 .selections
7070 .newest::<Point>(&snapshot.display_snapshot)
7071 .head();
7072 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7073 else {
7074 return;
7075 };
7076
7077 if self.blame.is_none() {
7078 self.start_git_blame(true, window, cx);
7079 }
7080 let Some(blame) = self.blame.as_ref() else {
7081 return;
7082 };
7083
7084 let row_info = RowInfo {
7085 buffer_id: Some(buffer.remote_id()),
7086 buffer_row: Some(point.row),
7087 ..Default::default()
7088 };
7089 let Some((buffer, blame_entry)) = blame
7090 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7091 .flatten()
7092 else {
7093 return;
7094 };
7095
7096 let anchor = self.selections.newest_anchor().head();
7097 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7098 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7099 self.show_blame_popover(
7100 buffer,
7101 &blame_entry,
7102 position + last_bounds.origin,
7103 true,
7104 cx,
7105 );
7106 };
7107 }
7108
7109 fn show_blame_popover(
7110 &mut self,
7111 buffer: BufferId,
7112 blame_entry: &BlameEntry,
7113 position: gpui::Point<Pixels>,
7114 ignore_timeout: bool,
7115 cx: &mut Context<Self>,
7116 ) {
7117 if let Some(state) = &mut self.inline_blame_popover {
7118 state.hide_task.take();
7119 } else {
7120 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7121 let blame_entry = blame_entry.clone();
7122 let show_task = cx.spawn(async move |editor, cx| {
7123 if !ignore_timeout {
7124 cx.background_executor()
7125 .timer(std::time::Duration::from_millis(blame_popover_delay))
7126 .await;
7127 }
7128 editor
7129 .update(cx, |editor, cx| {
7130 editor.inline_blame_popover_show_task.take();
7131 let Some(blame) = editor.blame.as_ref() else {
7132 return;
7133 };
7134 let blame = blame.read(cx);
7135 let details = blame.details_for_entry(buffer, &blame_entry);
7136 let markdown = cx.new(|cx| {
7137 Markdown::new(
7138 details
7139 .as_ref()
7140 .map(|message| message.message.clone())
7141 .unwrap_or_default(),
7142 None,
7143 None,
7144 cx,
7145 )
7146 });
7147 editor.inline_blame_popover = Some(InlineBlamePopover {
7148 position,
7149 hide_task: None,
7150 popover_bounds: None,
7151 popover_state: InlineBlamePopoverState {
7152 scroll_handle: ScrollHandle::new(),
7153 commit_message: details,
7154 markdown,
7155 },
7156 keyboard_grace: ignore_timeout,
7157 });
7158 cx.notify();
7159 })
7160 .ok();
7161 });
7162 self.inline_blame_popover_show_task = Some(show_task);
7163 }
7164 }
7165
7166 pub fn has_mouse_context_menu(&self) -> bool {
7167 self.mouse_context_menu.is_some()
7168 }
7169
7170 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7171 self.inline_blame_popover_show_task.take();
7172 if let Some(state) = &mut self.inline_blame_popover {
7173 let hide_task = cx.spawn(async move |editor, cx| {
7174 if !ignore_timeout {
7175 cx.background_executor()
7176 .timer(std::time::Duration::from_millis(100))
7177 .await;
7178 }
7179 editor
7180 .update(cx, |editor, cx| {
7181 editor.inline_blame_popover.take();
7182 cx.notify();
7183 })
7184 .ok();
7185 });
7186 state.hide_task = Some(hide_task);
7187 true
7188 } else {
7189 false
7190 }
7191 }
7192
7193 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7194 if self.pending_rename.is_some() {
7195 return None;
7196 }
7197
7198 let provider = self.semantics_provider.clone()?;
7199 let buffer = self.buffer.read(cx);
7200 let newest_selection = self.selections.newest_anchor().clone();
7201 let cursor_position = newest_selection.head();
7202 let (cursor_buffer, cursor_buffer_position) =
7203 buffer.text_anchor_for_position(cursor_position, cx)?;
7204 let (tail_buffer, tail_buffer_position) =
7205 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7206 if cursor_buffer != tail_buffer {
7207 return None;
7208 }
7209
7210 let snapshot = cursor_buffer.read(cx).snapshot();
7211 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7212 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7213 if start_word_range != end_word_range {
7214 self.document_highlights_task.take();
7215 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7216 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7217 return None;
7218 }
7219
7220 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7221 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7222 cx.background_executor()
7223 .timer(Duration::from_millis(debounce))
7224 .await;
7225
7226 let highlights = if let Some(highlights) = cx.update(|cx| {
7227 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7228 }) {
7229 highlights.await.log_err()
7230 } else {
7231 None
7232 };
7233
7234 if let Some(highlights) = highlights {
7235 this.update(cx, |this, cx| {
7236 if this.pending_rename.is_some() {
7237 return;
7238 }
7239
7240 let buffer = this.buffer.read(cx);
7241 if buffer
7242 .text_anchor_for_position(cursor_position, cx)
7243 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7244 {
7245 return;
7246 }
7247
7248 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7249 let mut write_ranges = Vec::new();
7250 let mut read_ranges = Vec::new();
7251 for highlight in highlights {
7252 let buffer_id = cursor_buffer.read(cx).remote_id();
7253 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7254 {
7255 let start = highlight
7256 .range
7257 .start
7258 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7259 let end = highlight
7260 .range
7261 .end
7262 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7263 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7264 continue;
7265 }
7266
7267 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7268 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7269 write_ranges.push(range);
7270 } else {
7271 read_ranges.push(range);
7272 }
7273 }
7274 }
7275
7276 this.highlight_background::<DocumentHighlightRead>(
7277 &read_ranges,
7278 |_, theme| theme.colors().editor_document_highlight_read_background,
7279 cx,
7280 );
7281 this.highlight_background::<DocumentHighlightWrite>(
7282 &write_ranges,
7283 |_, theme| theme.colors().editor_document_highlight_write_background,
7284 cx,
7285 );
7286 cx.notify();
7287 })
7288 .log_err();
7289 }
7290 }));
7291 None
7292 }
7293
7294 fn prepare_highlight_query_from_selection(
7295 &mut self,
7296 window: &Window,
7297 cx: &mut Context<Editor>,
7298 ) -> Option<(String, Range<Anchor>)> {
7299 if matches!(self.mode, EditorMode::SingleLine) {
7300 return None;
7301 }
7302 if !EditorSettings::get_global(cx).selection_highlight {
7303 return None;
7304 }
7305 if self.selections.count() != 1 || self.selections.line_mode() {
7306 return None;
7307 }
7308 let snapshot = self.snapshot(window, cx);
7309 let selection = self.selections.newest::<Point>(&snapshot);
7310 // If the selection spans multiple rows OR it is empty
7311 if selection.start.row != selection.end.row
7312 || selection.start.column == selection.end.column
7313 {
7314 return None;
7315 }
7316 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7317 let query = snapshot
7318 .buffer_snapshot()
7319 .text_for_range(selection_anchor_range.clone())
7320 .collect::<String>();
7321 if query.trim().is_empty() {
7322 return None;
7323 }
7324 Some((query, selection_anchor_range))
7325 }
7326
7327 #[ztracing::instrument(skip_all)]
7328 fn update_selection_occurrence_highlights(
7329 &mut self,
7330 query_text: String,
7331 query_range: Range<Anchor>,
7332 multi_buffer_range_to_query: Range<Point>,
7333 use_debounce: bool,
7334 window: &mut Window,
7335 cx: &mut Context<Editor>,
7336 ) -> Task<()> {
7337 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7338 cx.spawn_in(window, async move |editor, cx| {
7339 if use_debounce {
7340 cx.background_executor()
7341 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7342 .await;
7343 }
7344 let match_task = cx.background_spawn(async move {
7345 let buffer_ranges = multi_buffer_snapshot
7346 .range_to_buffer_ranges(multi_buffer_range_to_query)
7347 .into_iter()
7348 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7349 let mut match_ranges = Vec::new();
7350 let Ok(regex) = project::search::SearchQuery::text(
7351 query_text.clone(),
7352 false,
7353 false,
7354 false,
7355 Default::default(),
7356 Default::default(),
7357 false,
7358 None,
7359 ) else {
7360 return Vec::default();
7361 };
7362 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7363 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7364 match_ranges.extend(
7365 regex
7366 .search(
7367 buffer_snapshot,
7368 Some(search_range.start.0..search_range.end.0),
7369 )
7370 .await
7371 .into_iter()
7372 .filter_map(|match_range| {
7373 let match_start = buffer_snapshot
7374 .anchor_after(search_range.start + match_range.start);
7375 let match_end = buffer_snapshot
7376 .anchor_before(search_range.start + match_range.end);
7377 let match_anchor_range =
7378 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7379 (match_anchor_range != query_range).then_some(match_anchor_range)
7380 }),
7381 );
7382 }
7383 match_ranges
7384 });
7385 let match_ranges = match_task.await;
7386 editor
7387 .update_in(cx, |editor, _, cx| {
7388 if use_debounce {
7389 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7390 editor.debounced_selection_highlight_complete = true;
7391 } else if editor.debounced_selection_highlight_complete {
7392 return;
7393 }
7394 if !match_ranges.is_empty() {
7395 editor.highlight_background::<SelectedTextHighlight>(
7396 &match_ranges,
7397 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7398 cx,
7399 )
7400 }
7401 })
7402 .log_err();
7403 })
7404 }
7405
7406 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7407 struct NewlineFold;
7408 let type_id = std::any::TypeId::of::<NewlineFold>();
7409 if !self.mode.is_single_line() {
7410 return;
7411 }
7412 let snapshot = self.snapshot(window, cx);
7413 if snapshot.buffer_snapshot().max_point().row == 0 {
7414 return;
7415 }
7416 let task = cx.background_spawn(async move {
7417 let new_newlines = snapshot
7418 .buffer_chars_at(MultiBufferOffset(0))
7419 .filter_map(|(c, i)| {
7420 if c == '\n' {
7421 Some(
7422 snapshot.buffer_snapshot().anchor_after(i)
7423 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7424 )
7425 } else {
7426 None
7427 }
7428 })
7429 .collect::<Vec<_>>();
7430 let existing_newlines = snapshot
7431 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7432 .filter_map(|fold| {
7433 if fold.placeholder.type_tag == Some(type_id) {
7434 Some(fold.range.start..fold.range.end)
7435 } else {
7436 None
7437 }
7438 })
7439 .collect::<Vec<_>>();
7440
7441 (new_newlines, existing_newlines)
7442 });
7443 self.folding_newlines = cx.spawn(async move |this, cx| {
7444 let (new_newlines, existing_newlines) = task.await;
7445 if new_newlines == existing_newlines {
7446 return;
7447 }
7448 let placeholder = FoldPlaceholder {
7449 render: Arc::new(move |_, _, cx| {
7450 div()
7451 .bg(cx.theme().status().hint_background)
7452 .border_b_1()
7453 .size_full()
7454 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7455 .border_color(cx.theme().status().hint)
7456 .child("\\n")
7457 .into_any()
7458 }),
7459 constrain_width: false,
7460 merge_adjacent: false,
7461 type_tag: Some(type_id),
7462 };
7463 let creases = new_newlines
7464 .into_iter()
7465 .map(|range| Crease::simple(range, placeholder.clone()))
7466 .collect();
7467 this.update(cx, |this, cx| {
7468 this.display_map.update(cx, |display_map, cx| {
7469 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7470 display_map.fold(creases, cx);
7471 });
7472 })
7473 .ok();
7474 });
7475 }
7476
7477 #[ztracing::instrument(skip_all)]
7478 fn refresh_selected_text_highlights(
7479 &mut self,
7480 on_buffer_edit: bool,
7481 window: &mut Window,
7482 cx: &mut Context<Editor>,
7483 ) {
7484 let Some((query_text, query_range)) =
7485 self.prepare_highlight_query_from_selection(window, cx)
7486 else {
7487 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7488 self.quick_selection_highlight_task.take();
7489 self.debounced_selection_highlight_task.take();
7490 self.debounced_selection_highlight_complete = false;
7491 return;
7492 };
7493 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7494 let query_changed = self
7495 .quick_selection_highlight_task
7496 .as_ref()
7497 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7498 if query_changed {
7499 self.debounced_selection_highlight_complete = false;
7500 }
7501 if on_buffer_edit || query_changed {
7502 let multi_buffer_visible_start = self
7503 .scroll_manager
7504 .anchor()
7505 .anchor
7506 .to_point(&multi_buffer_snapshot);
7507 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7508 multi_buffer_visible_start
7509 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7510 Bias::Left,
7511 );
7512 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7513 self.quick_selection_highlight_task = Some((
7514 query_range.clone(),
7515 self.update_selection_occurrence_highlights(
7516 query_text.clone(),
7517 query_range.clone(),
7518 multi_buffer_visible_range,
7519 false,
7520 window,
7521 cx,
7522 ),
7523 ));
7524 }
7525 if on_buffer_edit
7526 || self
7527 .debounced_selection_highlight_task
7528 .as_ref()
7529 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7530 {
7531 let multi_buffer_start = multi_buffer_snapshot
7532 .anchor_before(MultiBufferOffset(0))
7533 .to_point(&multi_buffer_snapshot);
7534 let multi_buffer_end = multi_buffer_snapshot
7535 .anchor_after(multi_buffer_snapshot.len())
7536 .to_point(&multi_buffer_snapshot);
7537 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7538 self.debounced_selection_highlight_task = Some((
7539 query_range.clone(),
7540 self.update_selection_occurrence_highlights(
7541 query_text,
7542 query_range,
7543 multi_buffer_full_range,
7544 true,
7545 window,
7546 cx,
7547 ),
7548 ));
7549 }
7550 }
7551
7552 pub fn refresh_edit_prediction(
7553 &mut self,
7554 debounce: bool,
7555 user_requested: bool,
7556 window: &mut Window,
7557 cx: &mut Context<Self>,
7558 ) -> Option<()> {
7559 if DisableAiSettings::get_global(cx).disable_ai {
7560 return None;
7561 }
7562
7563 let provider = self.edit_prediction_provider()?;
7564 let cursor = self.selections.newest_anchor().head();
7565 let (buffer, cursor_buffer_position) =
7566 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7567
7568 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7569 self.discard_edit_prediction(false, cx);
7570 return None;
7571 }
7572
7573 self.update_visible_edit_prediction(window, cx);
7574
7575 if !user_requested
7576 && (!self.should_show_edit_predictions()
7577 || !self.is_focused(window)
7578 || buffer.read(cx).is_empty())
7579 {
7580 self.discard_edit_prediction(false, cx);
7581 return None;
7582 }
7583
7584 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7585 Some(())
7586 }
7587
7588 fn show_edit_predictions_in_menu(&self) -> bool {
7589 match self.edit_prediction_settings {
7590 EditPredictionSettings::Disabled => false,
7591 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7592 }
7593 }
7594
7595 pub fn edit_predictions_enabled(&self) -> bool {
7596 match self.edit_prediction_settings {
7597 EditPredictionSettings::Disabled => false,
7598 EditPredictionSettings::Enabled { .. } => true,
7599 }
7600 }
7601
7602 fn edit_prediction_requires_modifier(&self) -> bool {
7603 match self.edit_prediction_settings {
7604 EditPredictionSettings::Disabled => false,
7605 EditPredictionSettings::Enabled {
7606 preview_requires_modifier,
7607 ..
7608 } => preview_requires_modifier,
7609 }
7610 }
7611
7612 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7613 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7614 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7615 self.discard_edit_prediction(false, cx);
7616 } else {
7617 let selection = self.selections.newest_anchor();
7618 let cursor = selection.head();
7619
7620 if let Some((buffer, cursor_buffer_position)) =
7621 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7622 {
7623 self.edit_prediction_settings =
7624 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7625 }
7626 }
7627 }
7628
7629 fn edit_prediction_settings_at_position(
7630 &self,
7631 buffer: &Entity<Buffer>,
7632 buffer_position: language::Anchor,
7633 cx: &App,
7634 ) -> EditPredictionSettings {
7635 if !self.mode.is_full()
7636 || !self.show_edit_predictions_override.unwrap_or(true)
7637 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7638 {
7639 return EditPredictionSettings::Disabled;
7640 }
7641
7642 let buffer = buffer.read(cx);
7643
7644 let file = buffer.file();
7645
7646 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7647 return EditPredictionSettings::Disabled;
7648 };
7649
7650 let by_provider = matches!(
7651 self.menu_edit_predictions_policy,
7652 MenuEditPredictionsPolicy::ByProvider
7653 );
7654
7655 let show_in_menu = by_provider
7656 && self
7657 .edit_prediction_provider
7658 .as_ref()
7659 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7660
7661 let preview_requires_modifier =
7662 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7663
7664 EditPredictionSettings::Enabled {
7665 show_in_menu,
7666 preview_requires_modifier,
7667 }
7668 }
7669
7670 fn should_show_edit_predictions(&self) -> bool {
7671 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7672 }
7673
7674 pub fn edit_prediction_preview_is_active(&self) -> bool {
7675 matches!(
7676 self.edit_prediction_preview,
7677 EditPredictionPreview::Active { .. }
7678 )
7679 }
7680
7681 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7682 let cursor = self.selections.newest_anchor().head();
7683 if let Some((buffer, cursor_position)) =
7684 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7685 {
7686 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7687 } else {
7688 false
7689 }
7690 }
7691
7692 pub fn supports_minimap(&self, cx: &App) -> bool {
7693 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7694 }
7695
7696 fn edit_predictions_enabled_in_buffer(
7697 &self,
7698 buffer: &Entity<Buffer>,
7699 buffer_position: language::Anchor,
7700 cx: &App,
7701 ) -> bool {
7702 maybe!({
7703 if self.read_only(cx) {
7704 return Some(false);
7705 }
7706 let provider = self.edit_prediction_provider()?;
7707 if !provider.is_enabled(buffer, buffer_position, cx) {
7708 return Some(false);
7709 }
7710 let buffer = buffer.read(cx);
7711 let Some(file) = buffer.file() else {
7712 return Some(true);
7713 };
7714 let settings = all_language_settings(Some(file), cx);
7715 Some(settings.edit_predictions_enabled_for_file(file, cx))
7716 })
7717 .unwrap_or(false)
7718 }
7719
7720 pub fn show_edit_prediction(
7721 &mut self,
7722 _: &ShowEditPrediction,
7723 window: &mut Window,
7724 cx: &mut Context<Self>,
7725 ) {
7726 if !self.has_active_edit_prediction() {
7727 self.refresh_edit_prediction(false, true, window, cx);
7728 return;
7729 }
7730
7731 self.update_visible_edit_prediction(window, cx);
7732 }
7733
7734 pub fn display_cursor_names(
7735 &mut self,
7736 _: &DisplayCursorNames,
7737 window: &mut Window,
7738 cx: &mut Context<Self>,
7739 ) {
7740 self.show_cursor_names(window, cx);
7741 }
7742
7743 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7744 self.show_cursor_names = true;
7745 cx.notify();
7746 cx.spawn_in(window, async move |this, cx| {
7747 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7748 this.update(cx, |this, cx| {
7749 this.show_cursor_names = false;
7750 cx.notify()
7751 })
7752 .ok()
7753 })
7754 .detach();
7755 }
7756
7757 pub fn accept_partial_edit_prediction(
7758 &mut self,
7759 granularity: EditPredictionGranularity,
7760 window: &mut Window,
7761 cx: &mut Context<Self>,
7762 ) {
7763 if self.show_edit_predictions_in_menu() {
7764 self.hide_context_menu(window, cx);
7765 }
7766
7767 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7768 return;
7769 };
7770
7771 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7772 return;
7773 }
7774
7775 match &active_edit_prediction.completion {
7776 EditPrediction::MoveWithin { target, .. } => {
7777 let target = *target;
7778
7779 if matches!(granularity, EditPredictionGranularity::Full) {
7780 if let Some(position_map) = &self.last_position_map {
7781 let target_row = target.to_display_point(&position_map.snapshot).row();
7782 let is_visible = position_map.visible_row_range.contains(&target_row);
7783
7784 if is_visible || !self.edit_prediction_requires_modifier() {
7785 self.unfold_ranges(&[target..target], true, false, cx);
7786 self.change_selections(
7787 SelectionEffects::scroll(Autoscroll::newest()),
7788 window,
7789 cx,
7790 |selections| {
7791 selections.select_anchor_ranges([target..target]);
7792 },
7793 );
7794 self.clear_row_highlights::<EditPredictionPreview>();
7795 self.edit_prediction_preview
7796 .set_previous_scroll_position(None);
7797 } else {
7798 // Highlight and request scroll
7799 self.edit_prediction_preview
7800 .set_previous_scroll_position(Some(
7801 position_map.snapshot.scroll_anchor,
7802 ));
7803 self.highlight_rows::<EditPredictionPreview>(
7804 target..target,
7805 cx.theme().colors().editor_highlighted_line_background,
7806 RowHighlightOptions {
7807 autoscroll: true,
7808 ..Default::default()
7809 },
7810 cx,
7811 );
7812 self.request_autoscroll(Autoscroll::fit(), cx);
7813 }
7814 }
7815 } else {
7816 self.change_selections(
7817 SelectionEffects::scroll(Autoscroll::newest()),
7818 window,
7819 cx,
7820 |selections| {
7821 selections.select_anchor_ranges([target..target]);
7822 },
7823 );
7824 }
7825 }
7826 EditPrediction::MoveOutside { snapshot, target } => {
7827 if let Some(workspace) = self.workspace() {
7828 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7829 .detach_and_log_err(cx);
7830 }
7831 }
7832 EditPrediction::Edit { edits, .. } => {
7833 self.report_edit_prediction_event(
7834 active_edit_prediction.completion_id.clone(),
7835 true,
7836 cx,
7837 );
7838
7839 match granularity {
7840 EditPredictionGranularity::Full => {
7841 if let Some(provider) = self.edit_prediction_provider() {
7842 provider.accept(cx);
7843 }
7844
7845 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7846 let snapshot = self.buffer.read(cx).snapshot(cx);
7847 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7848
7849 self.buffer.update(cx, |buffer, cx| {
7850 buffer.edit(edits.iter().cloned(), None, cx)
7851 });
7852
7853 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7854 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7855 });
7856
7857 let selections = self.selections.disjoint_anchors_arc();
7858 if let Some(transaction_id_now) =
7859 self.buffer.read(cx).last_transaction_id(cx)
7860 {
7861 if transaction_id_prev != Some(transaction_id_now) {
7862 self.selection_history
7863 .insert_transaction(transaction_id_now, selections);
7864 }
7865 }
7866
7867 self.update_visible_edit_prediction(window, cx);
7868 if self.active_edit_prediction.is_none() {
7869 self.refresh_edit_prediction(true, true, window, cx);
7870 }
7871 cx.notify();
7872 }
7873 _ => {
7874 let snapshot = self.buffer.read(cx).snapshot(cx);
7875 let cursor_offset = self
7876 .selections
7877 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7878 .head();
7879
7880 let insertion = edits.iter().find_map(|(range, text)| {
7881 let range = range.to_offset(&snapshot);
7882 if range.is_empty() && range.start == cursor_offset {
7883 Some(text)
7884 } else {
7885 None
7886 }
7887 });
7888
7889 if let Some(text) = insertion {
7890 let text_to_insert = match granularity {
7891 EditPredictionGranularity::Word => {
7892 let mut partial = text
7893 .chars()
7894 .by_ref()
7895 .take_while(|c| c.is_alphabetic())
7896 .collect::<String>();
7897 if partial.is_empty() {
7898 partial = text
7899 .chars()
7900 .by_ref()
7901 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7902 .collect::<String>();
7903 }
7904 partial
7905 }
7906 EditPredictionGranularity::Line => {
7907 if let Some(line) = text.split_inclusive('\n').next() {
7908 line.to_string()
7909 } else {
7910 text.to_string()
7911 }
7912 }
7913 EditPredictionGranularity::Full => unreachable!(),
7914 };
7915
7916 cx.emit(EditorEvent::InputHandled {
7917 utf16_range_to_replace: None,
7918 text: text_to_insert.clone().into(),
7919 });
7920
7921 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7922 self.refresh_edit_prediction(true, true, window, cx);
7923 cx.notify();
7924 } else {
7925 self.accept_partial_edit_prediction(
7926 EditPredictionGranularity::Full,
7927 window,
7928 cx,
7929 );
7930 }
7931 }
7932 }
7933 }
7934 }
7935
7936 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7937 }
7938
7939 pub fn accept_next_word_edit_prediction(
7940 &mut self,
7941 _: &AcceptNextWordEditPrediction,
7942 window: &mut Window,
7943 cx: &mut Context<Self>,
7944 ) {
7945 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7946 }
7947
7948 pub fn accept_next_line_edit_prediction(
7949 &mut self,
7950 _: &AcceptNextLineEditPrediction,
7951 window: &mut Window,
7952 cx: &mut Context<Self>,
7953 ) {
7954 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7955 }
7956
7957 pub fn accept_edit_prediction(
7958 &mut self,
7959 _: &AcceptEditPrediction,
7960 window: &mut Window,
7961 cx: &mut Context<Self>,
7962 ) {
7963 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7964 }
7965
7966 fn discard_edit_prediction(
7967 &mut self,
7968 should_report_edit_prediction_event: bool,
7969 cx: &mut Context<Self>,
7970 ) -> bool {
7971 if should_report_edit_prediction_event {
7972 let completion_id = self
7973 .active_edit_prediction
7974 .as_ref()
7975 .and_then(|active_completion| active_completion.completion_id.clone());
7976
7977 self.report_edit_prediction_event(completion_id, false, cx);
7978 }
7979
7980 if let Some(provider) = self.edit_prediction_provider() {
7981 provider.discard(cx);
7982 }
7983
7984 self.take_active_edit_prediction(cx)
7985 }
7986
7987 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7988 let Some(provider) = self.edit_prediction_provider() else {
7989 return;
7990 };
7991
7992 let Some((_, buffer, _)) = self
7993 .buffer
7994 .read(cx)
7995 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7996 else {
7997 return;
7998 };
7999
8000 let extension = buffer
8001 .read(cx)
8002 .file()
8003 .and_then(|file| Some(file.path().extension()?.to_string()));
8004
8005 let event_type = match accepted {
8006 true => "Edit Prediction Accepted",
8007 false => "Edit Prediction Discarded",
8008 };
8009 telemetry::event!(
8010 event_type,
8011 provider = provider.name(),
8012 prediction_id = id,
8013 suggestion_accepted = accepted,
8014 file_extension = extension,
8015 );
8016 }
8017
8018 fn open_editor_at_anchor(
8019 snapshot: &language::BufferSnapshot,
8020 target: language::Anchor,
8021 workspace: &Entity<Workspace>,
8022 window: &mut Window,
8023 cx: &mut App,
8024 ) -> Task<Result<()>> {
8025 workspace.update(cx, |workspace, cx| {
8026 let path = snapshot.file().map(|file| file.full_path(cx));
8027 let Some(path) =
8028 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8029 else {
8030 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8031 };
8032 let target = text::ToPoint::to_point(&target, snapshot);
8033 let item = workspace.open_path(path, None, true, window, cx);
8034 window.spawn(cx, async move |cx| {
8035 let Some(editor) = item.await?.downcast::<Editor>() else {
8036 return Ok(());
8037 };
8038 editor
8039 .update_in(cx, |editor, window, cx| {
8040 editor.go_to_singleton_buffer_point(target, window, cx);
8041 })
8042 .ok();
8043 anyhow::Ok(())
8044 })
8045 })
8046 }
8047
8048 pub fn has_active_edit_prediction(&self) -> bool {
8049 self.active_edit_prediction.is_some()
8050 }
8051
8052 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8053 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8054 return false;
8055 };
8056
8057 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8058 self.clear_highlights::<EditPredictionHighlight>(cx);
8059 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8060 true
8061 }
8062
8063 /// Returns true when we're displaying the edit prediction popover below the cursor
8064 /// like we are not previewing and the LSP autocomplete menu is visible
8065 /// or we are in `when_holding_modifier` mode.
8066 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8067 if self.edit_prediction_preview_is_active()
8068 || !self.show_edit_predictions_in_menu()
8069 || !self.edit_predictions_enabled()
8070 {
8071 return false;
8072 }
8073
8074 if self.has_visible_completions_menu() {
8075 return true;
8076 }
8077
8078 has_completion && self.edit_prediction_requires_modifier()
8079 }
8080
8081 fn handle_modifiers_changed(
8082 &mut self,
8083 modifiers: Modifiers,
8084 position_map: &PositionMap,
8085 window: &mut Window,
8086 cx: &mut Context<Self>,
8087 ) {
8088 // Ensure that the edit prediction preview is updated, even when not
8089 // enabled, if there's an active edit prediction preview.
8090 if self.show_edit_predictions_in_menu()
8091 || matches!(
8092 self.edit_prediction_preview,
8093 EditPredictionPreview::Active { .. }
8094 )
8095 {
8096 self.update_edit_prediction_preview(&modifiers, window, cx);
8097 }
8098
8099 self.update_selection_mode(&modifiers, position_map, window, cx);
8100
8101 let mouse_position = window.mouse_position();
8102 if !position_map.text_hitbox.is_hovered(window) {
8103 return;
8104 }
8105
8106 self.update_hovered_link(
8107 position_map.point_for_position(mouse_position),
8108 &position_map.snapshot,
8109 modifiers,
8110 window,
8111 cx,
8112 )
8113 }
8114
8115 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8116 match EditorSettings::get_global(cx).multi_cursor_modifier {
8117 MultiCursorModifier::Alt => modifiers.secondary(),
8118 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8119 }
8120 }
8121
8122 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8123 match EditorSettings::get_global(cx).multi_cursor_modifier {
8124 MultiCursorModifier::Alt => modifiers.alt,
8125 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8126 }
8127 }
8128
8129 fn columnar_selection_mode(
8130 modifiers: &Modifiers,
8131 cx: &mut Context<Self>,
8132 ) -> Option<ColumnarMode> {
8133 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8134 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8135 Some(ColumnarMode::FromMouse)
8136 } else if Self::is_alt_pressed(modifiers, cx) {
8137 Some(ColumnarMode::FromSelection)
8138 } else {
8139 None
8140 }
8141 } else {
8142 None
8143 }
8144 }
8145
8146 fn update_selection_mode(
8147 &mut self,
8148 modifiers: &Modifiers,
8149 position_map: &PositionMap,
8150 window: &mut Window,
8151 cx: &mut Context<Self>,
8152 ) {
8153 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8154 return;
8155 };
8156 if self.selections.pending_anchor().is_none() {
8157 return;
8158 }
8159
8160 let mouse_position = window.mouse_position();
8161 let point_for_position = position_map.point_for_position(mouse_position);
8162 let position = point_for_position.previous_valid;
8163
8164 self.select(
8165 SelectPhase::BeginColumnar {
8166 position,
8167 reset: false,
8168 mode,
8169 goal_column: point_for_position.exact_unclipped.column(),
8170 },
8171 window,
8172 cx,
8173 );
8174 }
8175
8176 fn update_edit_prediction_preview(
8177 &mut self,
8178 modifiers: &Modifiers,
8179 window: &mut Window,
8180 cx: &mut Context<Self>,
8181 ) {
8182 let mut modifiers_held = false;
8183
8184 // Check bindings for all granularities.
8185 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8186 let granularities = [
8187 EditPredictionGranularity::Full,
8188 EditPredictionGranularity::Line,
8189 EditPredictionGranularity::Word,
8190 ];
8191
8192 for granularity in granularities {
8193 if let Some(keystroke) = self
8194 .accept_edit_prediction_keybind(granularity, window, cx)
8195 .keystroke()
8196 {
8197 modifiers_held = modifiers_held
8198 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8199 }
8200 }
8201
8202 if modifiers_held {
8203 if matches!(
8204 self.edit_prediction_preview,
8205 EditPredictionPreview::Inactive { .. }
8206 ) {
8207 self.edit_prediction_preview = EditPredictionPreview::Active {
8208 previous_scroll_position: None,
8209 since: Instant::now(),
8210 };
8211
8212 self.update_visible_edit_prediction(window, cx);
8213 cx.notify();
8214 }
8215 } else if let EditPredictionPreview::Active {
8216 previous_scroll_position,
8217 since,
8218 } = self.edit_prediction_preview
8219 {
8220 if let (Some(previous_scroll_position), Some(position_map)) =
8221 (previous_scroll_position, self.last_position_map.as_ref())
8222 {
8223 self.set_scroll_position(
8224 previous_scroll_position
8225 .scroll_position(&position_map.snapshot.display_snapshot),
8226 window,
8227 cx,
8228 );
8229 }
8230
8231 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8232 released_too_fast: since.elapsed() < Duration::from_millis(200),
8233 };
8234 self.clear_row_highlights::<EditPredictionPreview>();
8235 self.update_visible_edit_prediction(window, cx);
8236 cx.notify();
8237 }
8238 }
8239
8240 fn update_visible_edit_prediction(
8241 &mut self,
8242 _window: &mut Window,
8243 cx: &mut Context<Self>,
8244 ) -> Option<()> {
8245 if DisableAiSettings::get_global(cx).disable_ai {
8246 return None;
8247 }
8248
8249 if self.ime_transaction.is_some() {
8250 self.discard_edit_prediction(false, cx);
8251 return None;
8252 }
8253
8254 let selection = self.selections.newest_anchor();
8255 let cursor = selection.head();
8256 let multibuffer = self.buffer.read(cx).snapshot(cx);
8257 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8258 let excerpt_id = cursor.excerpt_id;
8259
8260 let show_in_menu = self.show_edit_predictions_in_menu();
8261 let completions_menu_has_precedence = !show_in_menu
8262 && (self.context_menu.borrow().is_some()
8263 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8264
8265 if completions_menu_has_precedence
8266 || !offset_selection.is_empty()
8267 || self
8268 .active_edit_prediction
8269 .as_ref()
8270 .is_some_and(|completion| {
8271 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8272 return false;
8273 };
8274 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8275 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8276 !invalidation_range.contains(&offset_selection.head())
8277 })
8278 {
8279 self.discard_edit_prediction(false, cx);
8280 return None;
8281 }
8282
8283 self.take_active_edit_prediction(cx);
8284 let Some(provider) = self.edit_prediction_provider() else {
8285 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8286 return None;
8287 };
8288
8289 let (buffer, cursor_buffer_position) =
8290 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8291
8292 self.edit_prediction_settings =
8293 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8294
8295 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8296
8297 if self.edit_prediction_indent_conflict {
8298 let cursor_point = cursor.to_point(&multibuffer);
8299 let mut suggested_indent = None;
8300 multibuffer.suggested_indents_callback(
8301 cursor_point.row..cursor_point.row + 1,
8302 |_, indent| {
8303 suggested_indent = Some(indent);
8304 ControlFlow::Break(())
8305 },
8306 cx,
8307 );
8308
8309 if let Some(indent) = suggested_indent
8310 && indent.len == cursor_point.column
8311 {
8312 self.edit_prediction_indent_conflict = false;
8313 }
8314 }
8315
8316 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8317
8318 let (completion_id, edits, edit_preview) = match edit_prediction {
8319 edit_prediction_types::EditPrediction::Local {
8320 id,
8321 edits,
8322 edit_preview,
8323 } => (id, edits, edit_preview),
8324 edit_prediction_types::EditPrediction::Jump {
8325 id,
8326 snapshot,
8327 target,
8328 } => {
8329 if let Some(provider) = &self.edit_prediction_provider {
8330 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8331 }
8332 self.stale_edit_prediction_in_menu = None;
8333 self.active_edit_prediction = Some(EditPredictionState {
8334 inlay_ids: vec![],
8335 completion: EditPrediction::MoveOutside { snapshot, target },
8336 completion_id: id,
8337 invalidation_range: None,
8338 });
8339 cx.notify();
8340 return Some(());
8341 }
8342 };
8343
8344 let edits = edits
8345 .into_iter()
8346 .flat_map(|(range, new_text)| {
8347 Some((
8348 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8349 new_text,
8350 ))
8351 })
8352 .collect::<Vec<_>>();
8353 if edits.is_empty() {
8354 return None;
8355 }
8356
8357 let first_edit_start = edits.first().unwrap().0.start;
8358 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8359 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8360
8361 let last_edit_end = edits.last().unwrap().0.end;
8362 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8363 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8364
8365 let cursor_row = cursor.to_point(&multibuffer).row;
8366
8367 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8368
8369 let mut inlay_ids = Vec::new();
8370 let invalidation_row_range;
8371 let move_invalidation_row_range = if cursor_row < edit_start_row {
8372 Some(cursor_row..edit_end_row)
8373 } else if cursor_row > edit_end_row {
8374 Some(edit_start_row..cursor_row)
8375 } else {
8376 None
8377 };
8378 let supports_jump = self
8379 .edit_prediction_provider
8380 .as_ref()
8381 .map(|provider| provider.provider.supports_jump_to_edit())
8382 .unwrap_or(true);
8383
8384 let is_move = supports_jump
8385 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8386 let completion = if is_move {
8387 if let Some(provider) = &self.edit_prediction_provider {
8388 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8389 }
8390 invalidation_row_range =
8391 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8392 let target = first_edit_start;
8393 EditPrediction::MoveWithin { target, snapshot }
8394 } else {
8395 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8396 && !self.edit_predictions_hidden_for_vim_mode;
8397
8398 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8399 if provider.show_tab_accept_marker() {
8400 EditDisplayMode::TabAccept
8401 } else {
8402 EditDisplayMode::Inline
8403 }
8404 } else {
8405 EditDisplayMode::DiffPopover
8406 };
8407
8408 if show_completions_in_buffer {
8409 if let Some(provider) = &self.edit_prediction_provider {
8410 let suggestion_display_type = match display_mode {
8411 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8412 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8413 SuggestionDisplayType::GhostText
8414 }
8415 };
8416 provider.provider.did_show(suggestion_display_type, cx);
8417 }
8418 if edits
8419 .iter()
8420 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8421 {
8422 let mut inlays = Vec::new();
8423 for (range, new_text) in &edits {
8424 let inlay = Inlay::edit_prediction(
8425 post_inc(&mut self.next_inlay_id),
8426 range.start,
8427 new_text.as_ref(),
8428 );
8429 inlay_ids.push(inlay.id);
8430 inlays.push(inlay);
8431 }
8432
8433 self.splice_inlays(&[], inlays, cx);
8434 } else {
8435 let background_color = cx.theme().status().deleted_background;
8436 self.highlight_text::<EditPredictionHighlight>(
8437 edits.iter().map(|(range, _)| range.clone()).collect(),
8438 HighlightStyle {
8439 background_color: Some(background_color),
8440 ..Default::default()
8441 },
8442 cx,
8443 );
8444 }
8445 }
8446
8447 invalidation_row_range = edit_start_row..edit_end_row;
8448
8449 EditPrediction::Edit {
8450 edits,
8451 edit_preview,
8452 display_mode,
8453 snapshot,
8454 }
8455 };
8456
8457 let invalidation_range = multibuffer
8458 .anchor_before(Point::new(invalidation_row_range.start, 0))
8459 ..multibuffer.anchor_after(Point::new(
8460 invalidation_row_range.end,
8461 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8462 ));
8463
8464 self.stale_edit_prediction_in_menu = None;
8465 self.active_edit_prediction = Some(EditPredictionState {
8466 inlay_ids,
8467 completion,
8468 completion_id,
8469 invalidation_range: Some(invalidation_range),
8470 });
8471
8472 cx.notify();
8473
8474 Some(())
8475 }
8476
8477 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8478 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8479 }
8480
8481 fn clear_tasks(&mut self) {
8482 self.tasks.clear()
8483 }
8484
8485 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8486 if self.tasks.insert(key, value).is_some() {
8487 // This case should hopefully be rare, but just in case...
8488 log::error!(
8489 "multiple different run targets found on a single line, only the last target will be rendered"
8490 )
8491 }
8492 }
8493
8494 /// Get all display points of breakpoints that will be rendered within editor
8495 ///
8496 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8497 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8498 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8499 fn active_breakpoints(
8500 &self,
8501 range: Range<DisplayRow>,
8502 window: &mut Window,
8503 cx: &mut Context<Self>,
8504 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8505 let mut breakpoint_display_points = HashMap::default();
8506
8507 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8508 return breakpoint_display_points;
8509 };
8510
8511 let snapshot = self.snapshot(window, cx);
8512
8513 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8514 let Some(project) = self.project() else {
8515 return breakpoint_display_points;
8516 };
8517
8518 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8519 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8520
8521 for (buffer_snapshot, range, excerpt_id) in
8522 multi_buffer_snapshot.range_to_buffer_ranges(range)
8523 {
8524 let Some(buffer) = project
8525 .read(cx)
8526 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8527 else {
8528 continue;
8529 };
8530 let breakpoints = breakpoint_store.read(cx).breakpoints(
8531 &buffer,
8532 Some(
8533 buffer_snapshot.anchor_before(range.start)
8534 ..buffer_snapshot.anchor_after(range.end),
8535 ),
8536 buffer_snapshot,
8537 cx,
8538 );
8539 for (breakpoint, state) in breakpoints {
8540 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8541 let position = multi_buffer_anchor
8542 .to_point(&multi_buffer_snapshot)
8543 .to_display_point(&snapshot);
8544
8545 breakpoint_display_points.insert(
8546 position.row(),
8547 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8548 );
8549 }
8550 }
8551
8552 breakpoint_display_points
8553 }
8554
8555 fn breakpoint_context_menu(
8556 &self,
8557 anchor: Anchor,
8558 window: &mut Window,
8559 cx: &mut Context<Self>,
8560 ) -> Entity<ui::ContextMenu> {
8561 let weak_editor = cx.weak_entity();
8562 let focus_handle = self.focus_handle(cx);
8563
8564 let row = self
8565 .buffer
8566 .read(cx)
8567 .snapshot(cx)
8568 .summary_for_anchor::<Point>(&anchor)
8569 .row;
8570
8571 let breakpoint = self
8572 .breakpoint_at_row(row, window, cx)
8573 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8574
8575 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8576 "Edit Log Breakpoint"
8577 } else {
8578 "Set Log Breakpoint"
8579 };
8580
8581 let condition_breakpoint_msg = if breakpoint
8582 .as_ref()
8583 .is_some_and(|bp| bp.1.condition.is_some())
8584 {
8585 "Edit Condition Breakpoint"
8586 } else {
8587 "Set Condition Breakpoint"
8588 };
8589
8590 let hit_condition_breakpoint_msg = if breakpoint
8591 .as_ref()
8592 .is_some_and(|bp| bp.1.hit_condition.is_some())
8593 {
8594 "Edit Hit Condition Breakpoint"
8595 } else {
8596 "Set Hit Condition Breakpoint"
8597 };
8598
8599 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8600 "Unset Breakpoint"
8601 } else {
8602 "Set Breakpoint"
8603 };
8604
8605 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8606
8607 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8608 BreakpointState::Enabled => Some("Disable"),
8609 BreakpointState::Disabled => Some("Enable"),
8610 });
8611
8612 let (anchor, breakpoint) =
8613 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8614
8615 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8616 menu.on_blur_subscription(Subscription::new(|| {}))
8617 .context(focus_handle)
8618 .when(run_to_cursor, |this| {
8619 let weak_editor = weak_editor.clone();
8620 this.entry("Run to cursor", None, move |window, cx| {
8621 weak_editor
8622 .update(cx, |editor, cx| {
8623 editor.change_selections(
8624 SelectionEffects::no_scroll(),
8625 window,
8626 cx,
8627 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8628 );
8629 })
8630 .ok();
8631
8632 window.dispatch_action(Box::new(RunToCursor), cx);
8633 })
8634 .separator()
8635 })
8636 .when_some(toggle_state_msg, |this, msg| {
8637 this.entry(msg, None, {
8638 let weak_editor = weak_editor.clone();
8639 let breakpoint = breakpoint.clone();
8640 move |_window, cx| {
8641 weak_editor
8642 .update(cx, |this, cx| {
8643 this.edit_breakpoint_at_anchor(
8644 anchor,
8645 breakpoint.as_ref().clone(),
8646 BreakpointEditAction::InvertState,
8647 cx,
8648 );
8649 })
8650 .log_err();
8651 }
8652 })
8653 })
8654 .entry(set_breakpoint_msg, None, {
8655 let weak_editor = weak_editor.clone();
8656 let breakpoint = breakpoint.clone();
8657 move |_window, cx| {
8658 weak_editor
8659 .update(cx, |this, cx| {
8660 this.edit_breakpoint_at_anchor(
8661 anchor,
8662 breakpoint.as_ref().clone(),
8663 BreakpointEditAction::Toggle,
8664 cx,
8665 );
8666 })
8667 .log_err();
8668 }
8669 })
8670 .entry(log_breakpoint_msg, None, {
8671 let breakpoint = breakpoint.clone();
8672 let weak_editor = weak_editor.clone();
8673 move |window, cx| {
8674 weak_editor
8675 .update(cx, |this, cx| {
8676 this.add_edit_breakpoint_block(
8677 anchor,
8678 breakpoint.as_ref(),
8679 BreakpointPromptEditAction::Log,
8680 window,
8681 cx,
8682 );
8683 })
8684 .log_err();
8685 }
8686 })
8687 .entry(condition_breakpoint_msg, None, {
8688 let breakpoint = breakpoint.clone();
8689 let weak_editor = weak_editor.clone();
8690 move |window, cx| {
8691 weak_editor
8692 .update(cx, |this, cx| {
8693 this.add_edit_breakpoint_block(
8694 anchor,
8695 breakpoint.as_ref(),
8696 BreakpointPromptEditAction::Condition,
8697 window,
8698 cx,
8699 );
8700 })
8701 .log_err();
8702 }
8703 })
8704 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8705 weak_editor
8706 .update(cx, |this, cx| {
8707 this.add_edit_breakpoint_block(
8708 anchor,
8709 breakpoint.as_ref(),
8710 BreakpointPromptEditAction::HitCondition,
8711 window,
8712 cx,
8713 );
8714 })
8715 .log_err();
8716 })
8717 })
8718 }
8719
8720 fn render_breakpoint(
8721 &self,
8722 position: Anchor,
8723 row: DisplayRow,
8724 breakpoint: &Breakpoint,
8725 state: Option<BreakpointSessionState>,
8726 cx: &mut Context<Self>,
8727 ) -> IconButton {
8728 let is_rejected = state.is_some_and(|s| !s.verified);
8729 // Is it a breakpoint that shows up when hovering over gutter?
8730 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8731 (false, false),
8732 |PhantomBreakpointIndicator {
8733 is_active,
8734 display_row,
8735 collides_with_existing_breakpoint,
8736 }| {
8737 (
8738 is_active && display_row == row,
8739 collides_with_existing_breakpoint,
8740 )
8741 },
8742 );
8743
8744 let (color, icon) = {
8745 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8746 (false, false) => ui::IconName::DebugBreakpoint,
8747 (true, false) => ui::IconName::DebugLogBreakpoint,
8748 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8749 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8750 };
8751
8752 let theme_colors = cx.theme().colors();
8753
8754 let color = if is_phantom {
8755 if collides_with_existing {
8756 Color::Custom(
8757 theme_colors
8758 .debugger_accent
8759 .blend(theme_colors.text.opacity(0.6)),
8760 )
8761 } else {
8762 Color::Hint
8763 }
8764 } else if is_rejected {
8765 Color::Disabled
8766 } else {
8767 Color::Debugger
8768 };
8769
8770 (color, icon)
8771 };
8772
8773 let breakpoint = Arc::from(breakpoint.clone());
8774
8775 let alt_as_text = gpui::Keystroke {
8776 modifiers: Modifiers::secondary_key(),
8777 ..Default::default()
8778 };
8779 let primary_action_text = if breakpoint.is_disabled() {
8780 "Enable breakpoint"
8781 } else if is_phantom && !collides_with_existing {
8782 "Set breakpoint"
8783 } else {
8784 "Unset breakpoint"
8785 };
8786 let focus_handle = self.focus_handle.clone();
8787
8788 let meta = if is_rejected {
8789 SharedString::from("No executable code is associated with this line.")
8790 } else if collides_with_existing && !breakpoint.is_disabled() {
8791 SharedString::from(format!(
8792 "{alt_as_text}-click to disable,\nright-click for more options."
8793 ))
8794 } else {
8795 SharedString::from("Right-click for more options.")
8796 };
8797 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8798 .icon_size(IconSize::XSmall)
8799 .size(ui::ButtonSize::None)
8800 .when(is_rejected, |this| {
8801 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8802 })
8803 .icon_color(color)
8804 .style(ButtonStyle::Transparent)
8805 .on_click(cx.listener({
8806 move |editor, event: &ClickEvent, window, cx| {
8807 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8808 BreakpointEditAction::InvertState
8809 } else {
8810 BreakpointEditAction::Toggle
8811 };
8812
8813 window.focus(&editor.focus_handle(cx), cx);
8814 editor.edit_breakpoint_at_anchor(
8815 position,
8816 breakpoint.as_ref().clone(),
8817 edit_action,
8818 cx,
8819 );
8820 }
8821 }))
8822 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8823 editor.set_breakpoint_context_menu(
8824 row,
8825 Some(position),
8826 event.position(),
8827 window,
8828 cx,
8829 );
8830 }))
8831 .tooltip(move |_window, cx| {
8832 Tooltip::with_meta_in(
8833 primary_action_text,
8834 Some(&ToggleBreakpoint),
8835 meta.clone(),
8836 &focus_handle,
8837 cx,
8838 )
8839 })
8840 }
8841
8842 fn build_tasks_context(
8843 project: &Entity<Project>,
8844 buffer: &Entity<Buffer>,
8845 buffer_row: u32,
8846 tasks: &Arc<RunnableTasks>,
8847 cx: &mut Context<Self>,
8848 ) -> Task<Option<task::TaskContext>> {
8849 let position = Point::new(buffer_row, tasks.column);
8850 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8851 let location = Location {
8852 buffer: buffer.clone(),
8853 range: range_start..range_start,
8854 };
8855 // Fill in the environmental variables from the tree-sitter captures
8856 let mut captured_task_variables = TaskVariables::default();
8857 for (capture_name, value) in tasks.extra_variables.clone() {
8858 captured_task_variables.insert(
8859 task::VariableName::Custom(capture_name.into()),
8860 value.clone(),
8861 );
8862 }
8863 project.update(cx, |project, cx| {
8864 project.task_store().update(cx, |task_store, cx| {
8865 task_store.task_context_for_location(captured_task_variables, location, cx)
8866 })
8867 })
8868 }
8869
8870 pub fn spawn_nearest_task(
8871 &mut self,
8872 action: &SpawnNearestTask,
8873 window: &mut Window,
8874 cx: &mut Context<Self>,
8875 ) {
8876 let Some((workspace, _)) = self.workspace.clone() else {
8877 return;
8878 };
8879 let Some(project) = self.project.clone() else {
8880 return;
8881 };
8882
8883 // Try to find a closest, enclosing node using tree-sitter that has a task
8884 let Some((buffer, buffer_row, tasks)) = self
8885 .find_enclosing_node_task(cx)
8886 // Or find the task that's closest in row-distance.
8887 .or_else(|| self.find_closest_task(cx))
8888 else {
8889 return;
8890 };
8891
8892 let reveal_strategy = action.reveal;
8893 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8894 cx.spawn_in(window, async move |_, cx| {
8895 let context = task_context.await?;
8896 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8897
8898 let resolved = &mut resolved_task.resolved;
8899 resolved.reveal = reveal_strategy;
8900
8901 workspace
8902 .update_in(cx, |workspace, window, cx| {
8903 workspace.schedule_resolved_task(
8904 task_source_kind,
8905 resolved_task,
8906 false,
8907 window,
8908 cx,
8909 );
8910 })
8911 .ok()
8912 })
8913 .detach();
8914 }
8915
8916 fn find_closest_task(
8917 &mut self,
8918 cx: &mut Context<Self>,
8919 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8920 let cursor_row = self
8921 .selections
8922 .newest_adjusted(&self.display_snapshot(cx))
8923 .head()
8924 .row;
8925
8926 let ((buffer_id, row), tasks) = self
8927 .tasks
8928 .iter()
8929 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8930
8931 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8932 let tasks = Arc::new(tasks.to_owned());
8933 Some((buffer, *row, tasks))
8934 }
8935
8936 fn find_enclosing_node_task(
8937 &mut self,
8938 cx: &mut Context<Self>,
8939 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8940 let snapshot = self.buffer.read(cx).snapshot(cx);
8941 let offset = self
8942 .selections
8943 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8944 .head();
8945 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8946 let offset = excerpt.map_offset_to_buffer(offset);
8947 let buffer_id = excerpt.buffer().remote_id();
8948
8949 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8950 let mut cursor = layer.node().walk();
8951
8952 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8953 if cursor.node().end_byte() == offset.0 {
8954 cursor.goto_next_sibling();
8955 }
8956 }
8957
8958 // Ascend to the smallest ancestor that contains the range and has a task.
8959 loop {
8960 let node = cursor.node();
8961 let node_range = node.byte_range();
8962 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8963
8964 // Check if this node contains our offset
8965 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8966 // If it contains offset, check for task
8967 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8968 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8969 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8970 }
8971 }
8972
8973 if !cursor.goto_parent() {
8974 break;
8975 }
8976 }
8977 None
8978 }
8979
8980 fn render_run_indicator(
8981 &self,
8982 _style: &EditorStyle,
8983 is_active: bool,
8984 row: DisplayRow,
8985 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8986 cx: &mut Context<Self>,
8987 ) -> IconButton {
8988 let color = Color::Muted;
8989 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8990
8991 IconButton::new(
8992 ("run_indicator", row.0 as usize),
8993 ui::IconName::PlayOutlined,
8994 )
8995 .shape(ui::IconButtonShape::Square)
8996 .icon_size(IconSize::XSmall)
8997 .icon_color(color)
8998 .toggle_state(is_active)
8999 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9000 let quick_launch = match e {
9001 ClickEvent::Keyboard(_) => true,
9002 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9003 };
9004
9005 window.focus(&editor.focus_handle(cx), cx);
9006 editor.toggle_code_actions(
9007 &ToggleCodeActions {
9008 deployed_from: Some(CodeActionSource::RunMenu(row)),
9009 quick_launch,
9010 },
9011 window,
9012 cx,
9013 );
9014 }))
9015 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9016 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9017 }))
9018 }
9019
9020 pub fn context_menu_visible(&self) -> bool {
9021 !self.edit_prediction_preview_is_active()
9022 && self
9023 .context_menu
9024 .borrow()
9025 .as_ref()
9026 .is_some_and(|menu| menu.visible())
9027 }
9028
9029 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9030 self.context_menu
9031 .borrow()
9032 .as_ref()
9033 .map(|menu| menu.origin())
9034 }
9035
9036 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9037 self.context_menu_options = Some(options);
9038 }
9039
9040 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9041 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9042
9043 fn render_edit_prediction_popover(
9044 &mut self,
9045 text_bounds: &Bounds<Pixels>,
9046 content_origin: gpui::Point<Pixels>,
9047 right_margin: Pixels,
9048 editor_snapshot: &EditorSnapshot,
9049 visible_row_range: Range<DisplayRow>,
9050 scroll_top: ScrollOffset,
9051 scroll_bottom: ScrollOffset,
9052 line_layouts: &[LineWithInvisibles],
9053 line_height: Pixels,
9054 scroll_position: gpui::Point<ScrollOffset>,
9055 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9056 newest_selection_head: Option<DisplayPoint>,
9057 editor_width: Pixels,
9058 style: &EditorStyle,
9059 window: &mut Window,
9060 cx: &mut App,
9061 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9062 if self.mode().is_minimap() {
9063 return None;
9064 }
9065 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9066
9067 if self.edit_prediction_visible_in_cursor_popover(true) {
9068 return None;
9069 }
9070
9071 match &active_edit_prediction.completion {
9072 EditPrediction::MoveWithin { target, .. } => {
9073 let target_display_point = target.to_display_point(editor_snapshot);
9074
9075 if self.edit_prediction_requires_modifier() {
9076 if !self.edit_prediction_preview_is_active() {
9077 return None;
9078 }
9079
9080 self.render_edit_prediction_modifier_jump_popover(
9081 text_bounds,
9082 content_origin,
9083 visible_row_range,
9084 line_layouts,
9085 line_height,
9086 scroll_pixel_position,
9087 newest_selection_head,
9088 target_display_point,
9089 window,
9090 cx,
9091 )
9092 } else {
9093 self.render_edit_prediction_eager_jump_popover(
9094 text_bounds,
9095 content_origin,
9096 editor_snapshot,
9097 visible_row_range,
9098 scroll_top,
9099 scroll_bottom,
9100 line_height,
9101 scroll_pixel_position,
9102 target_display_point,
9103 editor_width,
9104 window,
9105 cx,
9106 )
9107 }
9108 }
9109 EditPrediction::Edit {
9110 display_mode: EditDisplayMode::Inline,
9111 ..
9112 } => None,
9113 EditPrediction::Edit {
9114 display_mode: EditDisplayMode::TabAccept,
9115 edits,
9116 ..
9117 } => {
9118 let range = &edits.first()?.0;
9119 let target_display_point = range.end.to_display_point(editor_snapshot);
9120
9121 self.render_edit_prediction_end_of_line_popover(
9122 "Accept",
9123 editor_snapshot,
9124 visible_row_range,
9125 target_display_point,
9126 line_height,
9127 scroll_pixel_position,
9128 content_origin,
9129 editor_width,
9130 window,
9131 cx,
9132 )
9133 }
9134 EditPrediction::Edit {
9135 edits,
9136 edit_preview,
9137 display_mode: EditDisplayMode::DiffPopover,
9138 snapshot,
9139 } => self.render_edit_prediction_diff_popover(
9140 text_bounds,
9141 content_origin,
9142 right_margin,
9143 editor_snapshot,
9144 visible_row_range,
9145 line_layouts,
9146 line_height,
9147 scroll_position,
9148 scroll_pixel_position,
9149 newest_selection_head,
9150 editor_width,
9151 style,
9152 edits,
9153 edit_preview,
9154 snapshot,
9155 window,
9156 cx,
9157 ),
9158 EditPrediction::MoveOutside { snapshot, .. } => {
9159 let mut element = self
9160 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9161 .into_any();
9162
9163 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9164 let origin_x = text_bounds.size.width - size.width - px(30.);
9165 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9166 element.prepaint_at(origin, window, cx);
9167
9168 Some((element, origin))
9169 }
9170 }
9171 }
9172
9173 fn render_edit_prediction_modifier_jump_popover(
9174 &mut self,
9175 text_bounds: &Bounds<Pixels>,
9176 content_origin: gpui::Point<Pixels>,
9177 visible_row_range: Range<DisplayRow>,
9178 line_layouts: &[LineWithInvisibles],
9179 line_height: Pixels,
9180 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9181 newest_selection_head: Option<DisplayPoint>,
9182 target_display_point: DisplayPoint,
9183 window: &mut Window,
9184 cx: &mut App,
9185 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9186 let scrolled_content_origin =
9187 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9188
9189 const SCROLL_PADDING_Y: Pixels = px(12.);
9190
9191 if target_display_point.row() < visible_row_range.start {
9192 return self.render_edit_prediction_scroll_popover(
9193 |_| SCROLL_PADDING_Y,
9194 IconName::ArrowUp,
9195 visible_row_range,
9196 line_layouts,
9197 newest_selection_head,
9198 scrolled_content_origin,
9199 window,
9200 cx,
9201 );
9202 } else if target_display_point.row() >= visible_row_range.end {
9203 return self.render_edit_prediction_scroll_popover(
9204 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9205 IconName::ArrowDown,
9206 visible_row_range,
9207 line_layouts,
9208 newest_selection_head,
9209 scrolled_content_origin,
9210 window,
9211 cx,
9212 );
9213 }
9214
9215 const POLE_WIDTH: Pixels = px(2.);
9216
9217 let line_layout =
9218 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9219 let target_column = target_display_point.column() as usize;
9220
9221 let target_x = line_layout.x_for_index(target_column);
9222 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9223 - scroll_pixel_position.y;
9224
9225 let flag_on_right = target_x < text_bounds.size.width / 2.;
9226
9227 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9228 border_color.l += 0.001;
9229
9230 let mut element = v_flex()
9231 .items_end()
9232 .when(flag_on_right, |el| el.items_start())
9233 .child(if flag_on_right {
9234 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9235 .rounded_bl(px(0.))
9236 .rounded_tl(px(0.))
9237 .border_l_2()
9238 .border_color(border_color)
9239 } else {
9240 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9241 .rounded_br(px(0.))
9242 .rounded_tr(px(0.))
9243 .border_r_2()
9244 .border_color(border_color)
9245 })
9246 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9247 .into_any();
9248
9249 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9250
9251 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9252 - point(
9253 if flag_on_right {
9254 POLE_WIDTH
9255 } else {
9256 size.width - POLE_WIDTH
9257 },
9258 size.height - line_height,
9259 );
9260
9261 origin.x = origin.x.max(content_origin.x);
9262
9263 element.prepaint_at(origin, window, cx);
9264
9265 Some((element, origin))
9266 }
9267
9268 fn render_edit_prediction_scroll_popover(
9269 &mut self,
9270 to_y: impl Fn(Size<Pixels>) -> Pixels,
9271 scroll_icon: IconName,
9272 visible_row_range: Range<DisplayRow>,
9273 line_layouts: &[LineWithInvisibles],
9274 newest_selection_head: Option<DisplayPoint>,
9275 scrolled_content_origin: gpui::Point<Pixels>,
9276 window: &mut Window,
9277 cx: &mut App,
9278 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9279 let mut element = self
9280 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9281 .into_any();
9282
9283 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9284
9285 let cursor = newest_selection_head?;
9286 let cursor_row_layout =
9287 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9288 let cursor_column = cursor.column() as usize;
9289
9290 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9291
9292 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9293
9294 element.prepaint_at(origin, window, cx);
9295 Some((element, origin))
9296 }
9297
9298 fn render_edit_prediction_eager_jump_popover(
9299 &mut self,
9300 text_bounds: &Bounds<Pixels>,
9301 content_origin: gpui::Point<Pixels>,
9302 editor_snapshot: &EditorSnapshot,
9303 visible_row_range: Range<DisplayRow>,
9304 scroll_top: ScrollOffset,
9305 scroll_bottom: ScrollOffset,
9306 line_height: Pixels,
9307 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9308 target_display_point: DisplayPoint,
9309 editor_width: Pixels,
9310 window: &mut Window,
9311 cx: &mut App,
9312 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9313 if target_display_point.row().as_f64() < scroll_top {
9314 let mut element = self
9315 .render_edit_prediction_line_popover(
9316 "Jump to Edit",
9317 Some(IconName::ArrowUp),
9318 window,
9319 cx,
9320 )
9321 .into_any();
9322
9323 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9324 let offset = point(
9325 (text_bounds.size.width - size.width) / 2.,
9326 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9327 );
9328
9329 let origin = text_bounds.origin + offset;
9330 element.prepaint_at(origin, window, cx);
9331 Some((element, origin))
9332 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9333 let mut element = self
9334 .render_edit_prediction_line_popover(
9335 "Jump to Edit",
9336 Some(IconName::ArrowDown),
9337 window,
9338 cx,
9339 )
9340 .into_any();
9341
9342 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9343 let offset = point(
9344 (text_bounds.size.width - size.width) / 2.,
9345 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9346 );
9347
9348 let origin = text_bounds.origin + offset;
9349 element.prepaint_at(origin, window, cx);
9350 Some((element, origin))
9351 } else {
9352 self.render_edit_prediction_end_of_line_popover(
9353 "Jump to Edit",
9354 editor_snapshot,
9355 visible_row_range,
9356 target_display_point,
9357 line_height,
9358 scroll_pixel_position,
9359 content_origin,
9360 editor_width,
9361 window,
9362 cx,
9363 )
9364 }
9365 }
9366
9367 fn render_edit_prediction_end_of_line_popover(
9368 self: &mut Editor,
9369 label: &'static str,
9370 editor_snapshot: &EditorSnapshot,
9371 visible_row_range: Range<DisplayRow>,
9372 target_display_point: DisplayPoint,
9373 line_height: Pixels,
9374 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9375 content_origin: gpui::Point<Pixels>,
9376 editor_width: Pixels,
9377 window: &mut Window,
9378 cx: &mut App,
9379 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9380 let target_line_end = DisplayPoint::new(
9381 target_display_point.row(),
9382 editor_snapshot.line_len(target_display_point.row()),
9383 );
9384
9385 let mut element = self
9386 .render_edit_prediction_line_popover(label, None, window, cx)
9387 .into_any();
9388
9389 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9390
9391 let line_origin =
9392 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9393
9394 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9395 let mut origin = start_point
9396 + line_origin
9397 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9398 origin.x = origin.x.max(content_origin.x);
9399
9400 let max_x = content_origin.x + editor_width - size.width;
9401
9402 if origin.x > max_x {
9403 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9404
9405 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9406 origin.y += offset;
9407 IconName::ArrowUp
9408 } else {
9409 origin.y -= offset;
9410 IconName::ArrowDown
9411 };
9412
9413 element = self
9414 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9415 .into_any();
9416
9417 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9418
9419 origin.x = content_origin.x + editor_width - size.width - px(2.);
9420 }
9421
9422 element.prepaint_at(origin, window, cx);
9423 Some((element, origin))
9424 }
9425
9426 fn render_edit_prediction_diff_popover(
9427 self: &Editor,
9428 text_bounds: &Bounds<Pixels>,
9429 content_origin: gpui::Point<Pixels>,
9430 right_margin: Pixels,
9431 editor_snapshot: &EditorSnapshot,
9432 visible_row_range: Range<DisplayRow>,
9433 line_layouts: &[LineWithInvisibles],
9434 line_height: Pixels,
9435 scroll_position: gpui::Point<ScrollOffset>,
9436 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9437 newest_selection_head: Option<DisplayPoint>,
9438 editor_width: Pixels,
9439 style: &EditorStyle,
9440 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9441 edit_preview: &Option<language::EditPreview>,
9442 snapshot: &language::BufferSnapshot,
9443 window: &mut Window,
9444 cx: &mut App,
9445 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9446 let edit_start = edits
9447 .first()
9448 .unwrap()
9449 .0
9450 .start
9451 .to_display_point(editor_snapshot);
9452 let edit_end = edits
9453 .last()
9454 .unwrap()
9455 .0
9456 .end
9457 .to_display_point(editor_snapshot);
9458
9459 let is_visible = visible_row_range.contains(&edit_start.row())
9460 || visible_row_range.contains(&edit_end.row());
9461 if !is_visible {
9462 return None;
9463 }
9464
9465 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9466 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9467 } else {
9468 // Fallback for providers without edit_preview
9469 crate::edit_prediction_fallback_text(edits, cx)
9470 };
9471
9472 let styled_text = highlighted_edits.to_styled_text(&style.text);
9473 let line_count = highlighted_edits.text.lines().count();
9474
9475 const BORDER_WIDTH: Pixels = px(1.);
9476
9477 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9478 let has_keybind = keybind.is_some();
9479
9480 let mut element = h_flex()
9481 .items_start()
9482 .child(
9483 h_flex()
9484 .bg(cx.theme().colors().editor_background)
9485 .border(BORDER_WIDTH)
9486 .shadow_xs()
9487 .border_color(cx.theme().colors().border)
9488 .rounded_l_lg()
9489 .when(line_count > 1, |el| el.rounded_br_lg())
9490 .pr_1()
9491 .child(styled_text),
9492 )
9493 .child(
9494 h_flex()
9495 .h(line_height + BORDER_WIDTH * 2.)
9496 .px_1p5()
9497 .gap_1()
9498 // Workaround: For some reason, there's a gap if we don't do this
9499 .ml(-BORDER_WIDTH)
9500 .shadow(vec![gpui::BoxShadow {
9501 color: gpui::black().opacity(0.05),
9502 offset: point(px(1.), px(1.)),
9503 blur_radius: px(2.),
9504 spread_radius: px(0.),
9505 }])
9506 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9507 .border(BORDER_WIDTH)
9508 .border_color(cx.theme().colors().border)
9509 .rounded_r_lg()
9510 .id("edit_prediction_diff_popover_keybind")
9511 .when(!has_keybind, |el| {
9512 let status_colors = cx.theme().status();
9513
9514 el.bg(status_colors.error_background)
9515 .border_color(status_colors.error.opacity(0.6))
9516 .child(Icon::new(IconName::Info).color(Color::Error))
9517 .cursor_default()
9518 .hoverable_tooltip(move |_window, cx| {
9519 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9520 })
9521 })
9522 .children(keybind),
9523 )
9524 .into_any();
9525
9526 let longest_row =
9527 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9528 let longest_line_width = if visible_row_range.contains(&longest_row) {
9529 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9530 } else {
9531 layout_line(
9532 longest_row,
9533 editor_snapshot,
9534 style,
9535 editor_width,
9536 |_| false,
9537 window,
9538 cx,
9539 )
9540 .width
9541 };
9542
9543 let viewport_bounds =
9544 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9545 right: -right_margin,
9546 ..Default::default()
9547 });
9548
9549 let x_after_longest = Pixels::from(
9550 ScrollPixelOffset::from(
9551 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9552 ) - scroll_pixel_position.x,
9553 );
9554
9555 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9556
9557 // Fully visible if it can be displayed within the window (allow overlapping other
9558 // panes). However, this is only allowed if the popover starts within text_bounds.
9559 let can_position_to_the_right = x_after_longest < text_bounds.right()
9560 && x_after_longest + element_bounds.width < viewport_bounds.right();
9561
9562 let mut origin = if can_position_to_the_right {
9563 point(
9564 x_after_longest,
9565 text_bounds.origin.y
9566 + Pixels::from(
9567 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9568 - scroll_pixel_position.y,
9569 ),
9570 )
9571 } else {
9572 let cursor_row = newest_selection_head.map(|head| head.row());
9573 let above_edit = edit_start
9574 .row()
9575 .0
9576 .checked_sub(line_count as u32)
9577 .map(DisplayRow);
9578 let below_edit = Some(edit_end.row() + 1);
9579 let above_cursor =
9580 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9581 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9582
9583 // Place the edit popover adjacent to the edit if there is a location
9584 // available that is onscreen and does not obscure the cursor. Otherwise,
9585 // place it adjacent to the cursor.
9586 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9587 .into_iter()
9588 .flatten()
9589 .find(|&start_row| {
9590 let end_row = start_row + line_count as u32;
9591 visible_row_range.contains(&start_row)
9592 && visible_row_range.contains(&end_row)
9593 && cursor_row
9594 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9595 })?;
9596
9597 content_origin
9598 + point(
9599 Pixels::from(-scroll_pixel_position.x),
9600 Pixels::from(
9601 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9602 ),
9603 )
9604 };
9605
9606 origin.x -= BORDER_WIDTH;
9607
9608 window.defer_draw(element, origin, 1);
9609
9610 // Do not return an element, since it will already be drawn due to defer_draw.
9611 None
9612 }
9613
9614 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9615 px(30.)
9616 }
9617
9618 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9619 if self.read_only(cx) {
9620 cx.theme().players().read_only()
9621 } else {
9622 self.style.as_ref().unwrap().local_player
9623 }
9624 }
9625
9626 fn render_edit_prediction_accept_keybind(
9627 &self,
9628 window: &mut Window,
9629 cx: &mut App,
9630 ) -> Option<AnyElement> {
9631 let accept_binding =
9632 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9633 let accept_keystroke = accept_binding.keystroke()?;
9634
9635 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9636
9637 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9638 Color::Accent
9639 } else {
9640 Color::Muted
9641 };
9642
9643 h_flex()
9644 .px_0p5()
9645 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9646 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9647 .text_size(TextSize::XSmall.rems(cx))
9648 .child(h_flex().children(ui::render_modifiers(
9649 accept_keystroke.modifiers(),
9650 PlatformStyle::platform(),
9651 Some(modifiers_color),
9652 Some(IconSize::XSmall.rems().into()),
9653 true,
9654 )))
9655 .when(is_platform_style_mac, |parent| {
9656 parent.child(accept_keystroke.key().to_string())
9657 })
9658 .when(!is_platform_style_mac, |parent| {
9659 parent.child(
9660 Key::new(
9661 util::capitalize(accept_keystroke.key()),
9662 Some(Color::Default),
9663 )
9664 .size(Some(IconSize::XSmall.rems().into())),
9665 )
9666 })
9667 .into_any()
9668 .into()
9669 }
9670
9671 fn render_edit_prediction_line_popover(
9672 &self,
9673 label: impl Into<SharedString>,
9674 icon: Option<IconName>,
9675 window: &mut Window,
9676 cx: &mut App,
9677 ) -> Stateful<Div> {
9678 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9679
9680 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9681 let has_keybind = keybind.is_some();
9682
9683 h_flex()
9684 .id("ep-line-popover")
9685 .py_0p5()
9686 .pl_1()
9687 .pr(padding_right)
9688 .gap_1()
9689 .rounded_md()
9690 .border_1()
9691 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9692 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9693 .shadow_xs()
9694 .when(!has_keybind, |el| {
9695 let status_colors = cx.theme().status();
9696
9697 el.bg(status_colors.error_background)
9698 .border_color(status_colors.error.opacity(0.6))
9699 .pl_2()
9700 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9701 .cursor_default()
9702 .hoverable_tooltip(move |_window, cx| {
9703 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9704 })
9705 })
9706 .children(keybind)
9707 .child(
9708 Label::new(label)
9709 .size(LabelSize::Small)
9710 .when(!has_keybind, |el| {
9711 el.color(cx.theme().status().error.into()).strikethrough()
9712 }),
9713 )
9714 .when(!has_keybind, |el| {
9715 el.child(
9716 h_flex().ml_1().child(
9717 Icon::new(IconName::Info)
9718 .size(IconSize::Small)
9719 .color(cx.theme().status().error.into()),
9720 ),
9721 )
9722 })
9723 .when_some(icon, |element, icon| {
9724 element.child(
9725 div()
9726 .mt(px(1.5))
9727 .child(Icon::new(icon).size(IconSize::Small)),
9728 )
9729 })
9730 }
9731
9732 fn render_edit_prediction_jump_outside_popover(
9733 &self,
9734 snapshot: &BufferSnapshot,
9735 window: &mut Window,
9736 cx: &mut App,
9737 ) -> Stateful<Div> {
9738 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9739 let has_keybind = keybind.is_some();
9740
9741 let file_name = snapshot
9742 .file()
9743 .map(|file| SharedString::new(file.file_name(cx)))
9744 .unwrap_or(SharedString::new_static("untitled"));
9745
9746 h_flex()
9747 .id("ep-jump-outside-popover")
9748 .py_1()
9749 .px_2()
9750 .gap_1()
9751 .rounded_md()
9752 .border_1()
9753 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9754 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9755 .shadow_xs()
9756 .when(!has_keybind, |el| {
9757 let status_colors = cx.theme().status();
9758
9759 el.bg(status_colors.error_background)
9760 .border_color(status_colors.error.opacity(0.6))
9761 .pl_2()
9762 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9763 .cursor_default()
9764 .hoverable_tooltip(move |_window, cx| {
9765 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9766 })
9767 })
9768 .children(keybind)
9769 .child(
9770 Label::new(file_name)
9771 .size(LabelSize::Small)
9772 .buffer_font(cx)
9773 .when(!has_keybind, |el| {
9774 el.color(cx.theme().status().error.into()).strikethrough()
9775 }),
9776 )
9777 .when(!has_keybind, |el| {
9778 el.child(
9779 h_flex().ml_1().child(
9780 Icon::new(IconName::Info)
9781 .size(IconSize::Small)
9782 .color(cx.theme().status().error.into()),
9783 ),
9784 )
9785 })
9786 .child(
9787 div()
9788 .mt(px(1.5))
9789 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9790 )
9791 }
9792
9793 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9794 let accent_color = cx.theme().colors().text_accent;
9795 let editor_bg_color = cx.theme().colors().editor_background;
9796 editor_bg_color.blend(accent_color.opacity(0.1))
9797 }
9798
9799 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9800 let accent_color = cx.theme().colors().text_accent;
9801 let editor_bg_color = cx.theme().colors().editor_background;
9802 editor_bg_color.blend(accent_color.opacity(0.6))
9803 }
9804 fn get_prediction_provider_icon_name(
9805 provider: &Option<RegisteredEditPredictionDelegate>,
9806 ) -> IconName {
9807 match provider {
9808 Some(provider) => match provider.provider.name() {
9809 "copilot" => IconName::Copilot,
9810 "supermaven" => IconName::Supermaven,
9811 _ => IconName::ZedPredict,
9812 },
9813 None => IconName::ZedPredict,
9814 }
9815 }
9816
9817 fn render_edit_prediction_cursor_popover(
9818 &self,
9819 min_width: Pixels,
9820 max_width: Pixels,
9821 cursor_point: Point,
9822 style: &EditorStyle,
9823 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9824 _window: &Window,
9825 cx: &mut Context<Editor>,
9826 ) -> Option<AnyElement> {
9827 let provider = self.edit_prediction_provider.as_ref()?;
9828 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9829
9830 let is_refreshing = provider.provider.is_refreshing(cx);
9831
9832 fn pending_completion_container(icon: IconName) -> Div {
9833 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9834 }
9835
9836 let completion = match &self.active_edit_prediction {
9837 Some(prediction) => {
9838 if !self.has_visible_completions_menu() {
9839 const RADIUS: Pixels = px(6.);
9840 const BORDER_WIDTH: Pixels = px(1.);
9841
9842 return Some(
9843 h_flex()
9844 .elevation_2(cx)
9845 .border(BORDER_WIDTH)
9846 .border_color(cx.theme().colors().border)
9847 .when(accept_keystroke.is_none(), |el| {
9848 el.border_color(cx.theme().status().error)
9849 })
9850 .rounded(RADIUS)
9851 .rounded_tl(px(0.))
9852 .overflow_hidden()
9853 .child(div().px_1p5().child(match &prediction.completion {
9854 EditPrediction::MoveWithin { target, snapshot } => {
9855 use text::ToPoint as _;
9856 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9857 {
9858 Icon::new(IconName::ZedPredictDown)
9859 } else {
9860 Icon::new(IconName::ZedPredictUp)
9861 }
9862 }
9863 EditPrediction::MoveOutside { .. } => {
9864 // TODO [zeta2] custom icon for external jump?
9865 Icon::new(provider_icon)
9866 }
9867 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9868 }))
9869 .child(
9870 h_flex()
9871 .gap_1()
9872 .py_1()
9873 .px_2()
9874 .rounded_r(RADIUS - BORDER_WIDTH)
9875 .border_l_1()
9876 .border_color(cx.theme().colors().border)
9877 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9878 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9879 el.child(
9880 Label::new("Hold")
9881 .size(LabelSize::Small)
9882 .when(accept_keystroke.is_none(), |el| {
9883 el.strikethrough()
9884 })
9885 .line_height_style(LineHeightStyle::UiLabel),
9886 )
9887 })
9888 .id("edit_prediction_cursor_popover_keybind")
9889 .when(accept_keystroke.is_none(), |el| {
9890 let status_colors = cx.theme().status();
9891
9892 el.bg(status_colors.error_background)
9893 .border_color(status_colors.error.opacity(0.6))
9894 .child(Icon::new(IconName::Info).color(Color::Error))
9895 .cursor_default()
9896 .hoverable_tooltip(move |_window, cx| {
9897 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9898 .into()
9899 })
9900 })
9901 .when_some(
9902 accept_keystroke.as_ref(),
9903 |el, accept_keystroke| {
9904 el.child(h_flex().children(ui::render_modifiers(
9905 accept_keystroke.modifiers(),
9906 PlatformStyle::platform(),
9907 Some(Color::Default),
9908 Some(IconSize::XSmall.rems().into()),
9909 false,
9910 )))
9911 },
9912 ),
9913 )
9914 .into_any(),
9915 );
9916 }
9917
9918 self.render_edit_prediction_cursor_popover_preview(
9919 prediction,
9920 cursor_point,
9921 style,
9922 cx,
9923 )?
9924 }
9925
9926 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9927 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9928 stale_completion,
9929 cursor_point,
9930 style,
9931 cx,
9932 )?,
9933
9934 None => pending_completion_container(provider_icon)
9935 .child(Label::new("...").size(LabelSize::Small)),
9936 },
9937
9938 None => pending_completion_container(provider_icon)
9939 .child(Label::new("...").size(LabelSize::Small)),
9940 };
9941
9942 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9943 completion
9944 .with_animation(
9945 "loading-completion",
9946 Animation::new(Duration::from_secs(2))
9947 .repeat()
9948 .with_easing(pulsating_between(0.4, 0.8)),
9949 |label, delta| label.opacity(delta),
9950 )
9951 .into_any_element()
9952 } else {
9953 completion.into_any_element()
9954 };
9955
9956 let has_completion = self.active_edit_prediction.is_some();
9957
9958 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9959 Some(
9960 h_flex()
9961 .min_w(min_width)
9962 .max_w(max_width)
9963 .flex_1()
9964 .elevation_2(cx)
9965 .border_color(cx.theme().colors().border)
9966 .child(
9967 div()
9968 .flex_1()
9969 .py_1()
9970 .px_2()
9971 .overflow_hidden()
9972 .child(completion),
9973 )
9974 .when_some(accept_keystroke, |el, accept_keystroke| {
9975 if !accept_keystroke.modifiers().modified() {
9976 return el;
9977 }
9978
9979 el.child(
9980 h_flex()
9981 .h_full()
9982 .border_l_1()
9983 .rounded_r_lg()
9984 .border_color(cx.theme().colors().border)
9985 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9986 .gap_1()
9987 .py_1()
9988 .px_2()
9989 .child(
9990 h_flex()
9991 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9992 .when(is_platform_style_mac, |parent| parent.gap_1())
9993 .child(h_flex().children(ui::render_modifiers(
9994 accept_keystroke.modifiers(),
9995 PlatformStyle::platform(),
9996 Some(if !has_completion {
9997 Color::Muted
9998 } else {
9999 Color::Default
10000 }),
10001 None,
10002 false,
10003 ))),
10004 )
10005 .child(Label::new("Preview").into_any_element())
10006 .opacity(if has_completion { 1.0 } else { 0.4 }),
10007 )
10008 })
10009 .into_any(),
10010 )
10011 }
10012
10013 fn render_edit_prediction_cursor_popover_preview(
10014 &self,
10015 completion: &EditPredictionState,
10016 cursor_point: Point,
10017 style: &EditorStyle,
10018 cx: &mut Context<Editor>,
10019 ) -> Option<Div> {
10020 use text::ToPoint as _;
10021
10022 fn render_relative_row_jump(
10023 prefix: impl Into<String>,
10024 current_row: u32,
10025 target_row: u32,
10026 ) -> Div {
10027 let (row_diff, arrow) = if target_row < current_row {
10028 (current_row - target_row, IconName::ArrowUp)
10029 } else {
10030 (target_row - current_row, IconName::ArrowDown)
10031 };
10032
10033 h_flex()
10034 .child(
10035 Label::new(format!("{}{}", prefix.into(), row_diff))
10036 .color(Color::Muted)
10037 .size(LabelSize::Small),
10038 )
10039 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10040 }
10041
10042 let supports_jump = self
10043 .edit_prediction_provider
10044 .as_ref()
10045 .map(|provider| provider.provider.supports_jump_to_edit())
10046 .unwrap_or(true);
10047
10048 match &completion.completion {
10049 EditPrediction::MoveWithin {
10050 target, snapshot, ..
10051 } => {
10052 if !supports_jump {
10053 return None;
10054 }
10055
10056 Some(
10057 h_flex()
10058 .px_2()
10059 .gap_2()
10060 .flex_1()
10061 .child(
10062 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10063 Icon::new(IconName::ZedPredictDown)
10064 } else {
10065 Icon::new(IconName::ZedPredictUp)
10066 },
10067 )
10068 .child(Label::new("Jump to Edit")),
10069 )
10070 }
10071 EditPrediction::MoveOutside { snapshot, .. } => {
10072 let file_name = snapshot
10073 .file()
10074 .map(|file| file.file_name(cx))
10075 .unwrap_or("untitled");
10076 Some(
10077 h_flex()
10078 .px_2()
10079 .gap_2()
10080 .flex_1()
10081 .child(Icon::new(IconName::ZedPredict))
10082 .child(Label::new(format!("Jump to {file_name}"))),
10083 )
10084 }
10085 EditPrediction::Edit {
10086 edits,
10087 edit_preview,
10088 snapshot,
10089 display_mode: _,
10090 } => {
10091 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10092
10093 let (highlighted_edits, has_more_lines) =
10094 if let Some(edit_preview) = edit_preview.as_ref() {
10095 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10096 .first_line_preview()
10097 } else {
10098 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10099 };
10100
10101 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10102 .with_default_highlights(&style.text, highlighted_edits.highlights);
10103
10104 let preview = h_flex()
10105 .gap_1()
10106 .min_w_16()
10107 .child(styled_text)
10108 .when(has_more_lines, |parent| parent.child("…"));
10109
10110 let left = if supports_jump && first_edit_row != cursor_point.row {
10111 render_relative_row_jump("", cursor_point.row, first_edit_row)
10112 .into_any_element()
10113 } else {
10114 let icon_name =
10115 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
10116 Icon::new(icon_name).into_any_element()
10117 };
10118
10119 Some(
10120 h_flex()
10121 .h_full()
10122 .flex_1()
10123 .gap_2()
10124 .pr_1()
10125 .overflow_x_hidden()
10126 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10127 .child(left)
10128 .child(preview),
10129 )
10130 }
10131 }
10132 }
10133
10134 pub fn render_context_menu(
10135 &mut self,
10136 max_height_in_lines: u32,
10137 window: &mut Window,
10138 cx: &mut Context<Editor>,
10139 ) -> Option<AnyElement> {
10140 let menu = self.context_menu.borrow();
10141 let menu = menu.as_ref()?;
10142 if !menu.visible() {
10143 return None;
10144 };
10145 self.style
10146 .as_ref()
10147 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10148 }
10149
10150 fn render_context_menu_aside(
10151 &mut self,
10152 max_size: Size<Pixels>,
10153 window: &mut Window,
10154 cx: &mut Context<Editor>,
10155 ) -> Option<AnyElement> {
10156 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10157 if menu.visible() {
10158 menu.render_aside(max_size, window, cx)
10159 } else {
10160 None
10161 }
10162 })
10163 }
10164
10165 fn hide_context_menu(
10166 &mut self,
10167 window: &mut Window,
10168 cx: &mut Context<Self>,
10169 ) -> Option<CodeContextMenu> {
10170 cx.notify();
10171 self.completion_tasks.clear();
10172 let context_menu = self.context_menu.borrow_mut().take();
10173 self.stale_edit_prediction_in_menu.take();
10174 self.update_visible_edit_prediction(window, cx);
10175 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10176 && let Some(completion_provider) = &self.completion_provider
10177 {
10178 completion_provider.selection_changed(None, window, cx);
10179 }
10180 context_menu
10181 }
10182
10183 fn show_snippet_choices(
10184 &mut self,
10185 choices: &Vec<String>,
10186 selection: Range<Anchor>,
10187 cx: &mut Context<Self>,
10188 ) {
10189 let Some((_, buffer, _)) = self
10190 .buffer()
10191 .read(cx)
10192 .excerpt_containing(selection.start, cx)
10193 else {
10194 return;
10195 };
10196 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10197 else {
10198 return;
10199 };
10200 if buffer != end_buffer {
10201 log::error!("expected anchor range to have matching buffer IDs");
10202 return;
10203 }
10204
10205 let id = post_inc(&mut self.next_completion_id);
10206 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10207 let mut context_menu = self.context_menu.borrow_mut();
10208 let old_menu = context_menu.take();
10209 *context_menu = Some(CodeContextMenu::Completions(
10210 CompletionsMenu::new_snippet_choices(
10211 id,
10212 true,
10213 choices,
10214 selection,
10215 buffer,
10216 old_menu.map(|menu| menu.primary_scroll_handle()),
10217 snippet_sort_order,
10218 ),
10219 ));
10220 }
10221
10222 pub fn insert_snippet(
10223 &mut self,
10224 insertion_ranges: &[Range<MultiBufferOffset>],
10225 snippet: Snippet,
10226 window: &mut Window,
10227 cx: &mut Context<Self>,
10228 ) -> Result<()> {
10229 struct Tabstop<T> {
10230 is_end_tabstop: bool,
10231 ranges: Vec<Range<T>>,
10232 choices: Option<Vec<String>>,
10233 }
10234
10235 let tabstops = self.buffer.update(cx, |buffer, cx| {
10236 let snippet_text: Arc<str> = snippet.text.clone().into();
10237 let edits = insertion_ranges
10238 .iter()
10239 .cloned()
10240 .map(|range| (range, snippet_text.clone()));
10241 let autoindent_mode = AutoindentMode::Block {
10242 original_indent_columns: Vec::new(),
10243 };
10244 buffer.edit(edits, Some(autoindent_mode), cx);
10245
10246 let snapshot = &*buffer.read(cx);
10247 let snippet = &snippet;
10248 snippet
10249 .tabstops
10250 .iter()
10251 .map(|tabstop| {
10252 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10253 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10254 });
10255 let mut tabstop_ranges = tabstop
10256 .ranges
10257 .iter()
10258 .flat_map(|tabstop_range| {
10259 let mut delta = 0_isize;
10260 insertion_ranges.iter().map(move |insertion_range| {
10261 let insertion_start = insertion_range.start + delta;
10262 delta += snippet.text.len() as isize
10263 - (insertion_range.end - insertion_range.start) as isize;
10264
10265 let start =
10266 (insertion_start + tabstop_range.start).min(snapshot.len());
10267 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10268 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10269 })
10270 })
10271 .collect::<Vec<_>>();
10272 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10273
10274 Tabstop {
10275 is_end_tabstop,
10276 ranges: tabstop_ranges,
10277 choices: tabstop.choices.clone(),
10278 }
10279 })
10280 .collect::<Vec<_>>()
10281 });
10282 if let Some(tabstop) = tabstops.first() {
10283 self.change_selections(Default::default(), window, cx, |s| {
10284 // Reverse order so that the first range is the newest created selection.
10285 // Completions will use it and autoscroll will prioritize it.
10286 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10287 });
10288
10289 if let Some(choices) = &tabstop.choices
10290 && let Some(selection) = tabstop.ranges.first()
10291 {
10292 self.show_snippet_choices(choices, selection.clone(), cx)
10293 }
10294
10295 // If we're already at the last tabstop and it's at the end of the snippet,
10296 // we're done, we don't need to keep the state around.
10297 if !tabstop.is_end_tabstop {
10298 let choices = tabstops
10299 .iter()
10300 .map(|tabstop| tabstop.choices.clone())
10301 .collect();
10302
10303 let ranges = tabstops
10304 .into_iter()
10305 .map(|tabstop| tabstop.ranges)
10306 .collect::<Vec<_>>();
10307
10308 self.snippet_stack.push(SnippetState {
10309 active_index: 0,
10310 ranges,
10311 choices,
10312 });
10313 }
10314
10315 // Check whether the just-entered snippet ends with an auto-closable bracket.
10316 if self.autoclose_regions.is_empty() {
10317 let snapshot = self.buffer.read(cx).snapshot(cx);
10318 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10319 let selection_head = selection.head();
10320 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10321 continue;
10322 };
10323
10324 let mut bracket_pair = None;
10325 let max_lookup_length = scope
10326 .brackets()
10327 .map(|(pair, _)| {
10328 pair.start
10329 .as_str()
10330 .chars()
10331 .count()
10332 .max(pair.end.as_str().chars().count())
10333 })
10334 .max();
10335 if let Some(max_lookup_length) = max_lookup_length {
10336 let next_text = snapshot
10337 .chars_at(selection_head)
10338 .take(max_lookup_length)
10339 .collect::<String>();
10340 let prev_text = snapshot
10341 .reversed_chars_at(selection_head)
10342 .take(max_lookup_length)
10343 .collect::<String>();
10344
10345 for (pair, enabled) in scope.brackets() {
10346 if enabled
10347 && pair.close
10348 && prev_text.starts_with(pair.start.as_str())
10349 && next_text.starts_with(pair.end.as_str())
10350 {
10351 bracket_pair = Some(pair.clone());
10352 break;
10353 }
10354 }
10355 }
10356
10357 if let Some(pair) = bracket_pair {
10358 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10359 let autoclose_enabled =
10360 self.use_autoclose && snapshot_settings.use_autoclose;
10361 if autoclose_enabled {
10362 let start = snapshot.anchor_after(selection_head);
10363 let end = snapshot.anchor_after(selection_head);
10364 self.autoclose_regions.push(AutocloseRegion {
10365 selection_id: selection.id,
10366 range: start..end,
10367 pair,
10368 });
10369 }
10370 }
10371 }
10372 }
10373 }
10374 Ok(())
10375 }
10376
10377 pub fn move_to_next_snippet_tabstop(
10378 &mut self,
10379 window: &mut Window,
10380 cx: &mut Context<Self>,
10381 ) -> bool {
10382 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10383 }
10384
10385 pub fn move_to_prev_snippet_tabstop(
10386 &mut self,
10387 window: &mut Window,
10388 cx: &mut Context<Self>,
10389 ) -> bool {
10390 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10391 }
10392
10393 pub fn move_to_snippet_tabstop(
10394 &mut self,
10395 bias: Bias,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) -> bool {
10399 if let Some(mut snippet) = self.snippet_stack.pop() {
10400 match bias {
10401 Bias::Left => {
10402 if snippet.active_index > 0 {
10403 snippet.active_index -= 1;
10404 } else {
10405 self.snippet_stack.push(snippet);
10406 return false;
10407 }
10408 }
10409 Bias::Right => {
10410 if snippet.active_index + 1 < snippet.ranges.len() {
10411 snippet.active_index += 1;
10412 } else {
10413 self.snippet_stack.push(snippet);
10414 return false;
10415 }
10416 }
10417 }
10418 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10419 self.change_selections(Default::default(), window, cx, |s| {
10420 // Reverse order so that the first range is the newest created selection.
10421 // Completions will use it and autoscroll will prioritize it.
10422 s.select_ranges(current_ranges.iter().rev().cloned())
10423 });
10424
10425 if let Some(choices) = &snippet.choices[snippet.active_index]
10426 && let Some(selection) = current_ranges.first()
10427 {
10428 self.show_snippet_choices(choices, selection.clone(), cx);
10429 }
10430
10431 // If snippet state is not at the last tabstop, push it back on the stack
10432 if snippet.active_index + 1 < snippet.ranges.len() {
10433 self.snippet_stack.push(snippet);
10434 }
10435 return true;
10436 }
10437 }
10438
10439 false
10440 }
10441
10442 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10443 self.transact(window, cx, |this, window, cx| {
10444 this.select_all(&SelectAll, window, cx);
10445 this.insert("", window, cx);
10446 });
10447 }
10448
10449 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10450 if self.read_only(cx) {
10451 return;
10452 }
10453 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10454 self.transact(window, cx, |this, window, cx| {
10455 this.select_autoclose_pair(window, cx);
10456
10457 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10458
10459 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10460 if !this.linked_edit_ranges.is_empty() {
10461 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10462 let snapshot = this.buffer.read(cx).snapshot(cx);
10463
10464 for selection in selections.iter() {
10465 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10466 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10467 if selection_start.buffer_id != selection_end.buffer_id {
10468 continue;
10469 }
10470 if let Some(ranges) =
10471 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10472 {
10473 for (buffer, entries) in ranges {
10474 linked_ranges.entry(buffer).or_default().extend(entries);
10475 }
10476 }
10477 }
10478 }
10479
10480 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10481 for selection in &mut selections {
10482 if selection.is_empty() {
10483 let old_head = selection.head();
10484 let mut new_head =
10485 movement::left(&display_map, old_head.to_display_point(&display_map))
10486 .to_point(&display_map);
10487 if let Some((buffer, line_buffer_range)) = display_map
10488 .buffer_snapshot()
10489 .buffer_line_for_row(MultiBufferRow(old_head.row))
10490 {
10491 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10492 let indent_len = match indent_size.kind {
10493 IndentKind::Space => {
10494 buffer.settings_at(line_buffer_range.start, cx).tab_size
10495 }
10496 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10497 };
10498 if old_head.column <= indent_size.len && old_head.column > 0 {
10499 let indent_len = indent_len.get();
10500 new_head = cmp::min(
10501 new_head,
10502 MultiBufferPoint::new(
10503 old_head.row,
10504 ((old_head.column - 1) / indent_len) * indent_len,
10505 ),
10506 );
10507 }
10508 }
10509
10510 selection.set_head(new_head, SelectionGoal::None);
10511 }
10512 }
10513
10514 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10515 this.insert("", window, cx);
10516 let empty_str: Arc<str> = Arc::from("");
10517 for (buffer, edits) in linked_ranges {
10518 let snapshot = buffer.read(cx).snapshot();
10519 use text::ToPoint as TP;
10520
10521 let edits = edits
10522 .into_iter()
10523 .map(|range| {
10524 let end_point = TP::to_point(&range.end, &snapshot);
10525 let mut start_point = TP::to_point(&range.start, &snapshot);
10526
10527 if end_point == start_point {
10528 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10529 .saturating_sub(1);
10530 start_point =
10531 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10532 };
10533
10534 (start_point..end_point, empty_str.clone())
10535 })
10536 .sorted_by_key(|(range, _)| range.start)
10537 .collect::<Vec<_>>();
10538 buffer.update(cx, |this, cx| {
10539 this.edit(edits, None, cx);
10540 })
10541 }
10542 this.refresh_edit_prediction(true, false, window, cx);
10543 refresh_linked_ranges(this, window, cx);
10544 });
10545 }
10546
10547 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10548 if self.read_only(cx) {
10549 return;
10550 }
10551 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10552 self.transact(window, cx, |this, window, cx| {
10553 this.change_selections(Default::default(), window, cx, |s| {
10554 s.move_with(|map, selection| {
10555 if selection.is_empty() {
10556 let cursor = movement::right(map, selection.head());
10557 selection.end = cursor;
10558 selection.reversed = true;
10559 selection.goal = SelectionGoal::None;
10560 }
10561 })
10562 });
10563 this.insert("", window, cx);
10564 this.refresh_edit_prediction(true, false, window, cx);
10565 });
10566 }
10567
10568 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10569 if self.mode.is_single_line() {
10570 cx.propagate();
10571 return;
10572 }
10573
10574 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10575 if self.move_to_prev_snippet_tabstop(window, cx) {
10576 return;
10577 }
10578 self.outdent(&Outdent, window, cx);
10579 }
10580
10581 pub fn next_snippet_tabstop(
10582 &mut self,
10583 _: &NextSnippetTabstop,
10584 window: &mut Window,
10585 cx: &mut Context<Self>,
10586 ) {
10587 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10588 cx.propagate();
10589 return;
10590 }
10591
10592 if self.move_to_next_snippet_tabstop(window, cx) {
10593 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10594 return;
10595 }
10596 cx.propagate();
10597 }
10598
10599 pub fn previous_snippet_tabstop(
10600 &mut self,
10601 _: &PreviousSnippetTabstop,
10602 window: &mut Window,
10603 cx: &mut Context<Self>,
10604 ) {
10605 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10606 cx.propagate();
10607 return;
10608 }
10609
10610 if self.move_to_prev_snippet_tabstop(window, cx) {
10611 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10612 return;
10613 }
10614 cx.propagate();
10615 }
10616
10617 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10618 if self.mode.is_single_line() {
10619 cx.propagate();
10620 return;
10621 }
10622
10623 if self.move_to_next_snippet_tabstop(window, cx) {
10624 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10625 return;
10626 }
10627 if self.read_only(cx) {
10628 return;
10629 }
10630 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10631 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10632 let buffer = self.buffer.read(cx);
10633 let snapshot = buffer.snapshot(cx);
10634 let rows_iter = selections.iter().map(|s| s.head().row);
10635 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10636
10637 let has_some_cursor_in_whitespace = selections
10638 .iter()
10639 .filter(|selection| selection.is_empty())
10640 .any(|selection| {
10641 let cursor = selection.head();
10642 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10643 cursor.column < current_indent.len
10644 });
10645
10646 let mut edits = Vec::new();
10647 let mut prev_edited_row = 0;
10648 let mut row_delta = 0;
10649 for selection in &mut selections {
10650 if selection.start.row != prev_edited_row {
10651 row_delta = 0;
10652 }
10653 prev_edited_row = selection.end.row;
10654
10655 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10656 if selection.is_empty() {
10657 let cursor = selection.head();
10658 let settings = buffer.language_settings_at(cursor, cx);
10659 if settings.indent_list_on_tab {
10660 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10661 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10662 row_delta = Self::indent_selection(
10663 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10664 );
10665 continue;
10666 }
10667 }
10668 }
10669 }
10670
10671 // If the selection is non-empty, then increase the indentation of the selected lines.
10672 if !selection.is_empty() {
10673 row_delta =
10674 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10675 continue;
10676 }
10677
10678 let cursor = selection.head();
10679 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10680 if let Some(suggested_indent) =
10681 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10682 {
10683 // Don't do anything if already at suggested indent
10684 // and there is any other cursor which is not
10685 if has_some_cursor_in_whitespace
10686 && cursor.column == current_indent.len
10687 && current_indent.len == suggested_indent.len
10688 {
10689 continue;
10690 }
10691
10692 // Adjust line and move cursor to suggested indent
10693 // if cursor is not at suggested indent
10694 if cursor.column < suggested_indent.len
10695 && cursor.column <= current_indent.len
10696 && current_indent.len <= suggested_indent.len
10697 {
10698 selection.start = Point::new(cursor.row, suggested_indent.len);
10699 selection.end = selection.start;
10700 if row_delta == 0 {
10701 edits.extend(Buffer::edit_for_indent_size_adjustment(
10702 cursor.row,
10703 current_indent,
10704 suggested_indent,
10705 ));
10706 row_delta = suggested_indent.len - current_indent.len;
10707 }
10708 continue;
10709 }
10710
10711 // If current indent is more than suggested indent
10712 // only move cursor to current indent and skip indent
10713 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10714 selection.start = Point::new(cursor.row, current_indent.len);
10715 selection.end = selection.start;
10716 continue;
10717 }
10718 }
10719
10720 // Otherwise, insert a hard or soft tab.
10721 let settings = buffer.language_settings_at(cursor, cx);
10722 let tab_size = if settings.hard_tabs {
10723 IndentSize::tab()
10724 } else {
10725 let tab_size = settings.tab_size.get();
10726 let indent_remainder = snapshot
10727 .text_for_range(Point::new(cursor.row, 0)..cursor)
10728 .flat_map(str::chars)
10729 .fold(row_delta % tab_size, |counter: u32, c| {
10730 if c == '\t' {
10731 0
10732 } else {
10733 (counter + 1) % tab_size
10734 }
10735 });
10736
10737 let chars_to_next_tab_stop = tab_size - indent_remainder;
10738 IndentSize::spaces(chars_to_next_tab_stop)
10739 };
10740 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10741 selection.end = selection.start;
10742 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10743 row_delta += tab_size.len;
10744 }
10745
10746 self.transact(window, cx, |this, window, cx| {
10747 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10748 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10749 this.refresh_edit_prediction(true, false, window, cx);
10750 });
10751 }
10752
10753 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10754 if self.read_only(cx) {
10755 return;
10756 }
10757 if self.mode.is_single_line() {
10758 cx.propagate();
10759 return;
10760 }
10761
10762 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10763 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10764 let mut prev_edited_row = 0;
10765 let mut row_delta = 0;
10766 let mut edits = Vec::new();
10767 let buffer = self.buffer.read(cx);
10768 let snapshot = buffer.snapshot(cx);
10769 for selection in &mut selections {
10770 if selection.start.row != prev_edited_row {
10771 row_delta = 0;
10772 }
10773 prev_edited_row = selection.end.row;
10774
10775 row_delta =
10776 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10777 }
10778
10779 self.transact(window, cx, |this, window, cx| {
10780 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10781 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10782 });
10783 }
10784
10785 fn indent_selection(
10786 buffer: &MultiBuffer,
10787 snapshot: &MultiBufferSnapshot,
10788 selection: &mut Selection<Point>,
10789 edits: &mut Vec<(Range<Point>, String)>,
10790 delta_for_start_row: u32,
10791 cx: &App,
10792 ) -> u32 {
10793 let settings = buffer.language_settings_at(selection.start, cx);
10794 let tab_size = settings.tab_size.get();
10795 let indent_kind = if settings.hard_tabs {
10796 IndentKind::Tab
10797 } else {
10798 IndentKind::Space
10799 };
10800 let mut start_row = selection.start.row;
10801 let mut end_row = selection.end.row + 1;
10802
10803 // If a selection ends at the beginning of a line, don't indent
10804 // that last line.
10805 if selection.end.column == 0 && selection.end.row > selection.start.row {
10806 end_row -= 1;
10807 }
10808
10809 // Avoid re-indenting a row that has already been indented by a
10810 // previous selection, but still update this selection's column
10811 // to reflect that indentation.
10812 if delta_for_start_row > 0 {
10813 start_row += 1;
10814 selection.start.column += delta_for_start_row;
10815 if selection.end.row == selection.start.row {
10816 selection.end.column += delta_for_start_row;
10817 }
10818 }
10819
10820 let mut delta_for_end_row = 0;
10821 let has_multiple_rows = start_row + 1 != end_row;
10822 for row in start_row..end_row {
10823 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10824 let indent_delta = match (current_indent.kind, indent_kind) {
10825 (IndentKind::Space, IndentKind::Space) => {
10826 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10827 IndentSize::spaces(columns_to_next_tab_stop)
10828 }
10829 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10830 (_, IndentKind::Tab) => IndentSize::tab(),
10831 };
10832
10833 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10834 0
10835 } else {
10836 selection.start.column
10837 };
10838 let row_start = Point::new(row, start);
10839 edits.push((
10840 row_start..row_start,
10841 indent_delta.chars().collect::<String>(),
10842 ));
10843
10844 // Update this selection's endpoints to reflect the indentation.
10845 if row == selection.start.row {
10846 selection.start.column += indent_delta.len;
10847 }
10848 if row == selection.end.row {
10849 selection.end.column += indent_delta.len;
10850 delta_for_end_row = indent_delta.len;
10851 }
10852 }
10853
10854 if selection.start.row == selection.end.row {
10855 delta_for_start_row + delta_for_end_row
10856 } else {
10857 delta_for_end_row
10858 }
10859 }
10860
10861 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10862 if self.read_only(cx) {
10863 return;
10864 }
10865 if self.mode.is_single_line() {
10866 cx.propagate();
10867 return;
10868 }
10869
10870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10871 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10872 let selections = self.selections.all::<Point>(&display_map);
10873 let mut deletion_ranges = Vec::new();
10874 let mut last_outdent = None;
10875 {
10876 let buffer = self.buffer.read(cx);
10877 let snapshot = buffer.snapshot(cx);
10878 for selection in &selections {
10879 let settings = buffer.language_settings_at(selection.start, cx);
10880 let tab_size = settings.tab_size.get();
10881 let mut rows = selection.spanned_rows(false, &display_map);
10882
10883 // Avoid re-outdenting a row that has already been outdented by a
10884 // previous selection.
10885 if let Some(last_row) = last_outdent
10886 && last_row == rows.start
10887 {
10888 rows.start = rows.start.next_row();
10889 }
10890 let has_multiple_rows = rows.len() > 1;
10891 for row in rows.iter_rows() {
10892 let indent_size = snapshot.indent_size_for_line(row);
10893 if indent_size.len > 0 {
10894 let deletion_len = match indent_size.kind {
10895 IndentKind::Space => {
10896 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10897 if columns_to_prev_tab_stop == 0 {
10898 tab_size
10899 } else {
10900 columns_to_prev_tab_stop
10901 }
10902 }
10903 IndentKind::Tab => 1,
10904 };
10905 let start = if has_multiple_rows
10906 || deletion_len > selection.start.column
10907 || indent_size.len < selection.start.column
10908 {
10909 0
10910 } else {
10911 selection.start.column - deletion_len
10912 };
10913 deletion_ranges.push(
10914 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10915 );
10916 last_outdent = Some(row);
10917 }
10918 }
10919 }
10920 }
10921
10922 self.transact(window, cx, |this, window, cx| {
10923 this.buffer.update(cx, |buffer, cx| {
10924 let empty_str: Arc<str> = Arc::default();
10925 buffer.edit(
10926 deletion_ranges
10927 .into_iter()
10928 .map(|range| (range, empty_str.clone())),
10929 None,
10930 cx,
10931 );
10932 });
10933 let selections = this
10934 .selections
10935 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10936 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10937 });
10938 }
10939
10940 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10941 if self.read_only(cx) {
10942 return;
10943 }
10944 if self.mode.is_single_line() {
10945 cx.propagate();
10946 return;
10947 }
10948
10949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10950 let selections = self
10951 .selections
10952 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10953 .into_iter()
10954 .map(|s| s.range());
10955
10956 self.transact(window, cx, |this, window, cx| {
10957 this.buffer.update(cx, |buffer, cx| {
10958 buffer.autoindent_ranges(selections, cx);
10959 });
10960 let selections = this
10961 .selections
10962 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10963 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10964 });
10965 }
10966
10967 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10968 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10969 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10970 let selections = self.selections.all::<Point>(&display_map);
10971
10972 let mut new_cursors = Vec::new();
10973 let mut edit_ranges = Vec::new();
10974 let mut selections = selections.iter().peekable();
10975 while let Some(selection) = selections.next() {
10976 let mut rows = selection.spanned_rows(false, &display_map);
10977
10978 // Accumulate contiguous regions of rows that we want to delete.
10979 while let Some(next_selection) = selections.peek() {
10980 let next_rows = next_selection.spanned_rows(false, &display_map);
10981 if next_rows.start <= rows.end {
10982 rows.end = next_rows.end;
10983 selections.next().unwrap();
10984 } else {
10985 break;
10986 }
10987 }
10988
10989 let buffer = display_map.buffer_snapshot();
10990 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10991 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10992 // If there's a line after the range, delete the \n from the end of the row range
10993 (
10994 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10995 rows.end,
10996 )
10997 } else {
10998 // If there isn't a line after the range, delete the \n from the line before the
10999 // start of the row range
11000 edit_start = edit_start.saturating_sub_usize(1);
11001 (buffer.len(), rows.start.previous_row())
11002 };
11003
11004 let text_layout_details = self.text_layout_details(window);
11005 let x = display_map.x_for_display_point(
11006 selection.head().to_display_point(&display_map),
11007 &text_layout_details,
11008 );
11009 let row = Point::new(target_row.0, 0)
11010 .to_display_point(&display_map)
11011 .row();
11012 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11013
11014 new_cursors.push((
11015 selection.id,
11016 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11017 SelectionGoal::None,
11018 ));
11019 edit_ranges.push(edit_start..edit_end);
11020 }
11021
11022 self.transact(window, cx, |this, window, cx| {
11023 let buffer = this.buffer.update(cx, |buffer, cx| {
11024 let empty_str: Arc<str> = Arc::default();
11025 buffer.edit(
11026 edit_ranges
11027 .into_iter()
11028 .map(|range| (range, empty_str.clone())),
11029 None,
11030 cx,
11031 );
11032 buffer.snapshot(cx)
11033 });
11034 let new_selections = new_cursors
11035 .into_iter()
11036 .map(|(id, cursor, goal)| {
11037 let cursor = cursor.to_point(&buffer);
11038 Selection {
11039 id,
11040 start: cursor,
11041 end: cursor,
11042 reversed: false,
11043 goal,
11044 }
11045 })
11046 .collect();
11047
11048 this.change_selections(Default::default(), window, cx, |s| {
11049 s.select(new_selections);
11050 });
11051 });
11052 }
11053
11054 pub fn join_lines_impl(
11055 &mut self,
11056 insert_whitespace: bool,
11057 window: &mut Window,
11058 cx: &mut Context<Self>,
11059 ) {
11060 if self.read_only(cx) {
11061 return;
11062 }
11063 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11064 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11065 let start = MultiBufferRow(selection.start.row);
11066 // Treat single line selections as if they include the next line. Otherwise this action
11067 // would do nothing for single line selections individual cursors.
11068 let end = if selection.start.row == selection.end.row {
11069 MultiBufferRow(selection.start.row + 1)
11070 } else {
11071 MultiBufferRow(selection.end.row)
11072 };
11073
11074 if let Some(last_row_range) = row_ranges.last_mut()
11075 && start <= last_row_range.end
11076 {
11077 last_row_range.end = end;
11078 continue;
11079 }
11080 row_ranges.push(start..end);
11081 }
11082
11083 let snapshot = self.buffer.read(cx).snapshot(cx);
11084 let mut cursor_positions = Vec::new();
11085 for row_range in &row_ranges {
11086 let anchor = snapshot.anchor_before(Point::new(
11087 row_range.end.previous_row().0,
11088 snapshot.line_len(row_range.end.previous_row()),
11089 ));
11090 cursor_positions.push(anchor..anchor);
11091 }
11092
11093 self.transact(window, cx, |this, window, cx| {
11094 for row_range in row_ranges.into_iter().rev() {
11095 for row in row_range.iter_rows().rev() {
11096 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11097 let next_line_row = row.next_row();
11098 let indent = snapshot.indent_size_for_line(next_line_row);
11099 let start_of_next_line = Point::new(next_line_row.0, indent.len);
11100
11101 let replace =
11102 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
11103 " "
11104 } else {
11105 ""
11106 };
11107
11108 this.buffer.update(cx, |buffer, cx| {
11109 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11110 });
11111 }
11112 }
11113
11114 this.change_selections(Default::default(), window, cx, |s| {
11115 s.select_anchor_ranges(cursor_positions)
11116 });
11117 });
11118 }
11119
11120 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11122 self.join_lines_impl(true, window, cx);
11123 }
11124
11125 pub fn sort_lines_case_sensitive(
11126 &mut self,
11127 _: &SortLinesCaseSensitive,
11128 window: &mut Window,
11129 cx: &mut Context<Self>,
11130 ) {
11131 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11132 }
11133
11134 pub fn sort_lines_by_length(
11135 &mut self,
11136 _: &SortLinesByLength,
11137 window: &mut Window,
11138 cx: &mut Context<Self>,
11139 ) {
11140 self.manipulate_immutable_lines(window, cx, |lines| {
11141 lines.sort_by_key(|&line| line.chars().count())
11142 })
11143 }
11144
11145 pub fn sort_lines_case_insensitive(
11146 &mut self,
11147 _: &SortLinesCaseInsensitive,
11148 window: &mut Window,
11149 cx: &mut Context<Self>,
11150 ) {
11151 self.manipulate_immutable_lines(window, cx, |lines| {
11152 lines.sort_by_key(|line| line.to_lowercase())
11153 })
11154 }
11155
11156 pub fn unique_lines_case_insensitive(
11157 &mut self,
11158 _: &UniqueLinesCaseInsensitive,
11159 window: &mut Window,
11160 cx: &mut Context<Self>,
11161 ) {
11162 self.manipulate_immutable_lines(window, cx, |lines| {
11163 let mut seen = HashSet::default();
11164 lines.retain(|line| seen.insert(line.to_lowercase()));
11165 })
11166 }
11167
11168 pub fn unique_lines_case_sensitive(
11169 &mut self,
11170 _: &UniqueLinesCaseSensitive,
11171 window: &mut Window,
11172 cx: &mut Context<Self>,
11173 ) {
11174 self.manipulate_immutable_lines(window, cx, |lines| {
11175 let mut seen = HashSet::default();
11176 lines.retain(|line| seen.insert(*line));
11177 })
11178 }
11179
11180 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11181 let snapshot = self.buffer.read(cx).snapshot(cx);
11182 for selection in self.selections.disjoint_anchors_arc().iter() {
11183 if snapshot
11184 .language_at(selection.start)
11185 .and_then(|lang| lang.config().wrap_characters.as_ref())
11186 .is_some()
11187 {
11188 return true;
11189 }
11190 }
11191 false
11192 }
11193
11194 fn wrap_selections_in_tag(
11195 &mut self,
11196 _: &WrapSelectionsInTag,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11201
11202 let snapshot = self.buffer.read(cx).snapshot(cx);
11203
11204 let mut edits = Vec::new();
11205 let mut boundaries = Vec::new();
11206
11207 for selection in self
11208 .selections
11209 .all_adjusted(&self.display_snapshot(cx))
11210 .iter()
11211 {
11212 let Some(wrap_config) = snapshot
11213 .language_at(selection.start)
11214 .and_then(|lang| lang.config().wrap_characters.clone())
11215 else {
11216 continue;
11217 };
11218
11219 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11220 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11221
11222 let start_before = snapshot.anchor_before(selection.start);
11223 let end_after = snapshot.anchor_after(selection.end);
11224
11225 edits.push((start_before..start_before, open_tag));
11226 edits.push((end_after..end_after, close_tag));
11227
11228 boundaries.push((
11229 start_before,
11230 end_after,
11231 wrap_config.start_prefix.len(),
11232 wrap_config.end_suffix.len(),
11233 ));
11234 }
11235
11236 if edits.is_empty() {
11237 return;
11238 }
11239
11240 self.transact(window, cx, |this, window, cx| {
11241 let buffer = this.buffer.update(cx, |buffer, cx| {
11242 buffer.edit(edits, None, cx);
11243 buffer.snapshot(cx)
11244 });
11245
11246 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11247 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11248 boundaries.into_iter()
11249 {
11250 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11251 let close_offset = end_after
11252 .to_offset(&buffer)
11253 .saturating_sub_usize(end_suffix_len);
11254 new_selections.push(open_offset..open_offset);
11255 new_selections.push(close_offset..close_offset);
11256 }
11257
11258 this.change_selections(Default::default(), window, cx, |s| {
11259 s.select_ranges(new_selections);
11260 });
11261
11262 this.request_autoscroll(Autoscroll::fit(), cx);
11263 });
11264 }
11265
11266 pub fn toggle_read_only(
11267 &mut self,
11268 _: &workspace::ToggleReadOnlyFile,
11269 _: &mut Window,
11270 cx: &mut Context<Self>,
11271 ) {
11272 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11273 buffer.update(cx, |buffer, cx| {
11274 buffer.set_capability(
11275 match buffer.capability() {
11276 Capability::ReadWrite => Capability::Read,
11277 Capability::Read => Capability::ReadWrite,
11278 Capability::ReadOnly => Capability::ReadOnly,
11279 },
11280 cx,
11281 );
11282 })
11283 }
11284 }
11285
11286 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11287 let Some(project) = self.project.clone() else {
11288 return;
11289 };
11290 self.reload(project, window, cx)
11291 .detach_and_notify_err(window, cx);
11292 }
11293
11294 pub fn restore_file(
11295 &mut self,
11296 _: &::git::RestoreFile,
11297 window: &mut Window,
11298 cx: &mut Context<Self>,
11299 ) {
11300 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11301 let mut buffer_ids = HashSet::default();
11302 let snapshot = self.buffer().read(cx).snapshot(cx);
11303 for selection in self
11304 .selections
11305 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11306 {
11307 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11308 }
11309
11310 let buffer = self.buffer().read(cx);
11311 let ranges = buffer_ids
11312 .into_iter()
11313 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11314 .collect::<Vec<_>>();
11315
11316 self.restore_hunks_in_ranges(ranges, window, cx);
11317 }
11318
11319 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11321 let selections = self
11322 .selections
11323 .all(&self.display_snapshot(cx))
11324 .into_iter()
11325 .map(|s| s.range())
11326 .collect();
11327 self.restore_hunks_in_ranges(selections, window, cx);
11328 }
11329
11330 pub fn restore_hunks_in_ranges(
11331 &mut self,
11332 ranges: Vec<Range<Point>>,
11333 window: &mut Window,
11334 cx: &mut Context<Editor>,
11335 ) {
11336 let mut revert_changes = HashMap::default();
11337 let chunk_by = self
11338 .snapshot(window, cx)
11339 .hunks_for_ranges(ranges)
11340 .into_iter()
11341 .chunk_by(|hunk| hunk.buffer_id);
11342 for (buffer_id, hunks) in &chunk_by {
11343 let hunks = hunks.collect::<Vec<_>>();
11344 for hunk in &hunks {
11345 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11346 }
11347 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11348 }
11349 drop(chunk_by);
11350 if !revert_changes.is_empty() {
11351 self.transact(window, cx, |editor, window, cx| {
11352 editor.restore(revert_changes, window, cx);
11353 });
11354 }
11355 }
11356
11357 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11358 if let Some(status) = self
11359 .addons
11360 .iter()
11361 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11362 {
11363 return Some(status);
11364 }
11365 self.project
11366 .as_ref()?
11367 .read(cx)
11368 .status_for_buffer_id(buffer_id, cx)
11369 }
11370
11371 pub fn open_active_item_in_terminal(
11372 &mut self,
11373 _: &OpenInTerminal,
11374 window: &mut Window,
11375 cx: &mut Context<Self>,
11376 ) {
11377 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11378 let project_path = buffer.read(cx).project_path(cx)?;
11379 let project = self.project()?.read(cx);
11380 let entry = project.entry_for_path(&project_path, cx)?;
11381 let parent = match &entry.canonical_path {
11382 Some(canonical_path) => canonical_path.to_path_buf(),
11383 None => project.absolute_path(&project_path, cx)?,
11384 }
11385 .parent()?
11386 .to_path_buf();
11387 Some(parent)
11388 }) {
11389 window.dispatch_action(
11390 OpenTerminal {
11391 working_directory,
11392 local: false,
11393 }
11394 .boxed_clone(),
11395 cx,
11396 );
11397 }
11398 }
11399
11400 fn set_breakpoint_context_menu(
11401 &mut self,
11402 display_row: DisplayRow,
11403 position: Option<Anchor>,
11404 clicked_point: gpui::Point<Pixels>,
11405 window: &mut Window,
11406 cx: &mut Context<Self>,
11407 ) {
11408 let source = self
11409 .buffer
11410 .read(cx)
11411 .snapshot(cx)
11412 .anchor_before(Point::new(display_row.0, 0u32));
11413
11414 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11415
11416 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11417 self,
11418 source,
11419 clicked_point,
11420 context_menu,
11421 window,
11422 cx,
11423 );
11424 }
11425
11426 fn add_edit_breakpoint_block(
11427 &mut self,
11428 anchor: Anchor,
11429 breakpoint: &Breakpoint,
11430 edit_action: BreakpointPromptEditAction,
11431 window: &mut Window,
11432 cx: &mut Context<Self>,
11433 ) {
11434 let weak_editor = cx.weak_entity();
11435 let bp_prompt = cx.new(|cx| {
11436 BreakpointPromptEditor::new(
11437 weak_editor,
11438 anchor,
11439 breakpoint.clone(),
11440 edit_action,
11441 window,
11442 cx,
11443 )
11444 });
11445
11446 let height = bp_prompt.update(cx, |this, cx| {
11447 this.prompt
11448 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11449 });
11450 let cloned_prompt = bp_prompt.clone();
11451 let blocks = vec![BlockProperties {
11452 style: BlockStyle::Sticky,
11453 placement: BlockPlacement::Above(anchor),
11454 height: Some(height),
11455 render: Arc::new(move |cx| {
11456 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11457 cloned_prompt.clone().into_any_element()
11458 }),
11459 priority: 0,
11460 }];
11461
11462 let focus_handle = bp_prompt.focus_handle(cx);
11463 window.focus(&focus_handle, cx);
11464
11465 let block_ids = self.insert_blocks(blocks, None, cx);
11466 bp_prompt.update(cx, |prompt, _| {
11467 prompt.add_block_ids(block_ids);
11468 });
11469 }
11470
11471 pub(crate) fn breakpoint_at_row(
11472 &self,
11473 row: u32,
11474 window: &mut Window,
11475 cx: &mut Context<Self>,
11476 ) -> Option<(Anchor, Breakpoint)> {
11477 let snapshot = self.snapshot(window, cx);
11478 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11479
11480 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11481 }
11482
11483 pub(crate) fn breakpoint_at_anchor(
11484 &self,
11485 breakpoint_position: Anchor,
11486 snapshot: &EditorSnapshot,
11487 cx: &mut Context<Self>,
11488 ) -> Option<(Anchor, Breakpoint)> {
11489 let buffer = self
11490 .buffer
11491 .read(cx)
11492 .buffer_for_anchor(breakpoint_position, cx)?;
11493
11494 let enclosing_excerpt = breakpoint_position.excerpt_id;
11495 let buffer_snapshot = buffer.read(cx).snapshot();
11496
11497 let row = buffer_snapshot
11498 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11499 .row;
11500
11501 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11502 let anchor_end = snapshot
11503 .buffer_snapshot()
11504 .anchor_after(Point::new(row, line_len));
11505
11506 self.breakpoint_store
11507 .as_ref()?
11508 .read_with(cx, |breakpoint_store, cx| {
11509 breakpoint_store
11510 .breakpoints(
11511 &buffer,
11512 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11513 &buffer_snapshot,
11514 cx,
11515 )
11516 .next()
11517 .and_then(|(bp, _)| {
11518 let breakpoint_row = buffer_snapshot
11519 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11520 .row;
11521
11522 if breakpoint_row == row {
11523 snapshot
11524 .buffer_snapshot()
11525 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11526 .map(|position| (position, bp.bp.clone()))
11527 } else {
11528 None
11529 }
11530 })
11531 })
11532 }
11533
11534 pub fn edit_log_breakpoint(
11535 &mut self,
11536 _: &EditLogBreakpoint,
11537 window: &mut Window,
11538 cx: &mut Context<Self>,
11539 ) {
11540 if self.breakpoint_store.is_none() {
11541 return;
11542 }
11543
11544 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11545 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11546 message: None,
11547 state: BreakpointState::Enabled,
11548 condition: None,
11549 hit_condition: None,
11550 });
11551
11552 self.add_edit_breakpoint_block(
11553 anchor,
11554 &breakpoint,
11555 BreakpointPromptEditAction::Log,
11556 window,
11557 cx,
11558 );
11559 }
11560 }
11561
11562 fn breakpoints_at_cursors(
11563 &self,
11564 window: &mut Window,
11565 cx: &mut Context<Self>,
11566 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11567 let snapshot = self.snapshot(window, cx);
11568 let cursors = self
11569 .selections
11570 .disjoint_anchors_arc()
11571 .iter()
11572 .map(|selection| {
11573 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11574
11575 let breakpoint_position = self
11576 .breakpoint_at_row(cursor_position.row, window, cx)
11577 .map(|bp| bp.0)
11578 .unwrap_or_else(|| {
11579 snapshot
11580 .display_snapshot
11581 .buffer_snapshot()
11582 .anchor_after(Point::new(cursor_position.row, 0))
11583 });
11584
11585 let breakpoint = self
11586 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11587 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11588
11589 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11590 })
11591 // 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.
11592 .collect::<HashMap<Anchor, _>>();
11593
11594 cursors.into_iter().collect()
11595 }
11596
11597 pub fn enable_breakpoint(
11598 &mut self,
11599 _: &crate::actions::EnableBreakpoint,
11600 window: &mut Window,
11601 cx: &mut Context<Self>,
11602 ) {
11603 if self.breakpoint_store.is_none() {
11604 return;
11605 }
11606
11607 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11608 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11609 continue;
11610 };
11611 self.edit_breakpoint_at_anchor(
11612 anchor,
11613 breakpoint,
11614 BreakpointEditAction::InvertState,
11615 cx,
11616 );
11617 }
11618 }
11619
11620 pub fn disable_breakpoint(
11621 &mut self,
11622 _: &crate::actions::DisableBreakpoint,
11623 window: &mut Window,
11624 cx: &mut Context<Self>,
11625 ) {
11626 if self.breakpoint_store.is_none() {
11627 return;
11628 }
11629
11630 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11631 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11632 continue;
11633 };
11634 self.edit_breakpoint_at_anchor(
11635 anchor,
11636 breakpoint,
11637 BreakpointEditAction::InvertState,
11638 cx,
11639 );
11640 }
11641 }
11642
11643 pub fn toggle_breakpoint(
11644 &mut self,
11645 _: &crate::actions::ToggleBreakpoint,
11646 window: &mut Window,
11647 cx: &mut Context<Self>,
11648 ) {
11649 if self.breakpoint_store.is_none() {
11650 return;
11651 }
11652
11653 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11654 if let Some(breakpoint) = breakpoint {
11655 self.edit_breakpoint_at_anchor(
11656 anchor,
11657 breakpoint,
11658 BreakpointEditAction::Toggle,
11659 cx,
11660 );
11661 } else {
11662 self.edit_breakpoint_at_anchor(
11663 anchor,
11664 Breakpoint::new_standard(),
11665 BreakpointEditAction::Toggle,
11666 cx,
11667 );
11668 }
11669 }
11670 }
11671
11672 pub fn edit_breakpoint_at_anchor(
11673 &mut self,
11674 breakpoint_position: Anchor,
11675 breakpoint: Breakpoint,
11676 edit_action: BreakpointEditAction,
11677 cx: &mut Context<Self>,
11678 ) {
11679 let Some(breakpoint_store) = &self.breakpoint_store else {
11680 return;
11681 };
11682
11683 let Some(buffer) = self
11684 .buffer
11685 .read(cx)
11686 .buffer_for_anchor(breakpoint_position, cx)
11687 else {
11688 return;
11689 };
11690
11691 breakpoint_store.update(cx, |breakpoint_store, cx| {
11692 breakpoint_store.toggle_breakpoint(
11693 buffer,
11694 BreakpointWithPosition {
11695 position: breakpoint_position.text_anchor,
11696 bp: breakpoint,
11697 },
11698 edit_action,
11699 cx,
11700 );
11701 });
11702
11703 cx.notify();
11704 }
11705
11706 #[cfg(any(test, feature = "test-support"))]
11707 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11708 self.breakpoint_store.clone()
11709 }
11710
11711 pub fn prepare_restore_change(
11712 &self,
11713 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11714 hunk: &MultiBufferDiffHunk,
11715 cx: &mut App,
11716 ) -> Option<()> {
11717 if hunk.is_created_file() {
11718 return None;
11719 }
11720 let buffer = self.buffer.read(cx);
11721 let diff = buffer.diff_for(hunk.buffer_id)?;
11722 let buffer = buffer.buffer(hunk.buffer_id)?;
11723 let buffer = buffer.read(cx);
11724 let original_text = diff
11725 .read(cx)
11726 .base_text(cx)
11727 .as_rope()
11728 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11729 let buffer_snapshot = buffer.snapshot();
11730 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11731 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11732 probe
11733 .0
11734 .start
11735 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11736 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11737 }) {
11738 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11739 Some(())
11740 } else {
11741 None
11742 }
11743 }
11744
11745 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11746 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11747 }
11748
11749 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11750 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11751 }
11752
11753 pub fn rotate_selections_forward(
11754 &mut self,
11755 _: &RotateSelectionsForward,
11756 window: &mut Window,
11757 cx: &mut Context<Self>,
11758 ) {
11759 self.rotate_selections(window, cx, false)
11760 }
11761
11762 pub fn rotate_selections_backward(
11763 &mut self,
11764 _: &RotateSelectionsBackward,
11765 window: &mut Window,
11766 cx: &mut Context<Self>,
11767 ) {
11768 self.rotate_selections(window, cx, true)
11769 }
11770
11771 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11773 let display_snapshot = self.display_snapshot(cx);
11774 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11775
11776 if selections.len() < 2 {
11777 return;
11778 }
11779
11780 let (edits, new_selections) = {
11781 let buffer = self.buffer.read(cx).read(cx);
11782 let has_selections = selections.iter().any(|s| !s.is_empty());
11783 if has_selections {
11784 let mut selected_texts: Vec<String> = selections
11785 .iter()
11786 .map(|selection| {
11787 buffer
11788 .text_for_range(selection.start..selection.end)
11789 .collect()
11790 })
11791 .collect();
11792
11793 if reverse {
11794 selected_texts.rotate_left(1);
11795 } else {
11796 selected_texts.rotate_right(1);
11797 }
11798
11799 let mut offset_delta: i64 = 0;
11800 let mut new_selections = Vec::new();
11801 let edits: Vec<_> = selections
11802 .iter()
11803 .zip(selected_texts.iter())
11804 .map(|(selection, new_text)| {
11805 let old_len = (selection.end.0 - selection.start.0) as i64;
11806 let new_len = new_text.len() as i64;
11807 let adjusted_start =
11808 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11809 let adjusted_end =
11810 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11811
11812 new_selections.push(Selection {
11813 id: selection.id,
11814 start: adjusted_start,
11815 end: adjusted_end,
11816 reversed: selection.reversed,
11817 goal: selection.goal,
11818 });
11819
11820 offset_delta += new_len - old_len;
11821 (selection.start..selection.end, new_text.clone())
11822 })
11823 .collect();
11824 (edits, new_selections)
11825 } else {
11826 let mut all_rows: Vec<u32> = selections
11827 .iter()
11828 .map(|selection| buffer.offset_to_point(selection.start).row)
11829 .collect();
11830 all_rows.sort_unstable();
11831 all_rows.dedup();
11832
11833 if all_rows.len() < 2 {
11834 return;
11835 }
11836
11837 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11838 .iter()
11839 .map(|&row| {
11840 let start = Point::new(row, 0);
11841 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11842 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11843 })
11844 .collect();
11845
11846 let mut line_texts: Vec<String> = line_ranges
11847 .iter()
11848 .map(|range| buffer.text_for_range(range.clone()).collect())
11849 .collect();
11850
11851 if reverse {
11852 line_texts.rotate_left(1);
11853 } else {
11854 line_texts.rotate_right(1);
11855 }
11856
11857 let edits = line_ranges
11858 .iter()
11859 .zip(line_texts.iter())
11860 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11861 .collect();
11862
11863 let num_rows = all_rows.len();
11864 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11865 .iter()
11866 .enumerate()
11867 .map(|(i, &row)| (row, i))
11868 .collect();
11869
11870 // Compute new line start offsets after rotation (handles CRLF)
11871 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11872 let first_line_start = line_ranges[0].start.0;
11873 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11874 for text in line_texts.iter().take(num_rows - 1) {
11875 let prev_start = *new_line_starts.last().unwrap();
11876 new_line_starts.push(prev_start + text.len() + newline_len);
11877 }
11878
11879 let new_selections = selections
11880 .iter()
11881 .map(|selection| {
11882 let point = buffer.offset_to_point(selection.start);
11883 let old_index = row_to_index[&point.row];
11884 let new_index = if reverse {
11885 (old_index + num_rows - 1) % num_rows
11886 } else {
11887 (old_index + 1) % num_rows
11888 };
11889 let new_offset =
11890 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11891 Selection {
11892 id: selection.id,
11893 start: new_offset,
11894 end: new_offset,
11895 reversed: selection.reversed,
11896 goal: selection.goal,
11897 }
11898 })
11899 .collect();
11900
11901 (edits, new_selections)
11902 }
11903 };
11904
11905 self.transact(window, cx, |this, window, cx| {
11906 this.buffer.update(cx, |buffer, cx| {
11907 buffer.edit(edits, None, cx);
11908 });
11909 this.change_selections(Default::default(), window, cx, |s| {
11910 s.select(new_selections);
11911 });
11912 });
11913 }
11914
11915 fn manipulate_lines<M>(
11916 &mut self,
11917 window: &mut Window,
11918 cx: &mut Context<Self>,
11919 mut manipulate: M,
11920 ) where
11921 M: FnMut(&str) -> LineManipulationResult,
11922 {
11923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11924
11925 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11926 let buffer = self.buffer.read(cx).snapshot(cx);
11927
11928 let mut edits = Vec::new();
11929
11930 let selections = self.selections.all::<Point>(&display_map);
11931 let mut selections = selections.iter().peekable();
11932 let mut contiguous_row_selections = Vec::new();
11933 let mut new_selections = Vec::new();
11934 let mut added_lines = 0;
11935 let mut removed_lines = 0;
11936
11937 while let Some(selection) = selections.next() {
11938 let (start_row, end_row) = consume_contiguous_rows(
11939 &mut contiguous_row_selections,
11940 selection,
11941 &display_map,
11942 &mut selections,
11943 );
11944
11945 let start_point = Point::new(start_row.0, 0);
11946 let end_point = Point::new(
11947 end_row.previous_row().0,
11948 buffer.line_len(end_row.previous_row()),
11949 );
11950 let text = buffer
11951 .text_for_range(start_point..end_point)
11952 .collect::<String>();
11953
11954 let LineManipulationResult {
11955 new_text,
11956 line_count_before,
11957 line_count_after,
11958 } = manipulate(&text);
11959
11960 edits.push((start_point..end_point, new_text));
11961
11962 // Selections must change based on added and removed line count
11963 let start_row =
11964 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11965 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11966 new_selections.push(Selection {
11967 id: selection.id,
11968 start: start_row,
11969 end: end_row,
11970 goal: SelectionGoal::None,
11971 reversed: selection.reversed,
11972 });
11973
11974 if line_count_after > line_count_before {
11975 added_lines += line_count_after - line_count_before;
11976 } else if line_count_before > line_count_after {
11977 removed_lines += line_count_before - line_count_after;
11978 }
11979 }
11980
11981 self.transact(window, cx, |this, window, cx| {
11982 let buffer = this.buffer.update(cx, |buffer, cx| {
11983 buffer.edit(edits, None, cx);
11984 buffer.snapshot(cx)
11985 });
11986
11987 // Recalculate offsets on newly edited buffer
11988 let new_selections = new_selections
11989 .iter()
11990 .map(|s| {
11991 let start_point = Point::new(s.start.0, 0);
11992 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11993 Selection {
11994 id: s.id,
11995 start: buffer.point_to_offset(start_point),
11996 end: buffer.point_to_offset(end_point),
11997 goal: s.goal,
11998 reversed: s.reversed,
11999 }
12000 })
12001 .collect();
12002
12003 this.change_selections(Default::default(), window, cx, |s| {
12004 s.select(new_selections);
12005 });
12006
12007 this.request_autoscroll(Autoscroll::fit(), cx);
12008 });
12009 }
12010
12011 fn manipulate_immutable_lines<Fn>(
12012 &mut self,
12013 window: &mut Window,
12014 cx: &mut Context<Self>,
12015 mut callback: Fn,
12016 ) where
12017 Fn: FnMut(&mut Vec<&str>),
12018 {
12019 self.manipulate_lines(window, cx, |text| {
12020 let mut lines: Vec<&str> = text.split('\n').collect();
12021 let line_count_before = lines.len();
12022
12023 callback(&mut lines);
12024
12025 LineManipulationResult {
12026 new_text: lines.join("\n"),
12027 line_count_before,
12028 line_count_after: lines.len(),
12029 }
12030 });
12031 }
12032
12033 fn manipulate_mutable_lines<Fn>(
12034 &mut self,
12035 window: &mut Window,
12036 cx: &mut Context<Self>,
12037 mut callback: Fn,
12038 ) where
12039 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12040 {
12041 self.manipulate_lines(window, cx, |text| {
12042 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12043 let line_count_before = lines.len();
12044
12045 callback(&mut lines);
12046
12047 LineManipulationResult {
12048 new_text: lines.join("\n"),
12049 line_count_before,
12050 line_count_after: lines.len(),
12051 }
12052 });
12053 }
12054
12055 pub fn convert_indentation_to_spaces(
12056 &mut self,
12057 _: &ConvertIndentationToSpaces,
12058 window: &mut Window,
12059 cx: &mut Context<Self>,
12060 ) {
12061 let settings = self.buffer.read(cx).language_settings(cx);
12062 let tab_size = settings.tab_size.get() as usize;
12063
12064 self.manipulate_mutable_lines(window, cx, |lines| {
12065 // Allocates a reasonably sized scratch buffer once for the whole loop
12066 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12067 // Avoids recomputing spaces that could be inserted many times
12068 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12069 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12070 .collect();
12071
12072 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12073 let mut chars = line.as_ref().chars();
12074 let mut col = 0;
12075 let mut changed = false;
12076
12077 for ch in chars.by_ref() {
12078 match ch {
12079 ' ' => {
12080 reindented_line.push(' ');
12081 col += 1;
12082 }
12083 '\t' => {
12084 // \t are converted to spaces depending on the current column
12085 let spaces_len = tab_size - (col % tab_size);
12086 reindented_line.extend(&space_cache[spaces_len - 1]);
12087 col += spaces_len;
12088 changed = true;
12089 }
12090 _ => {
12091 // If we dont append before break, the character is consumed
12092 reindented_line.push(ch);
12093 break;
12094 }
12095 }
12096 }
12097
12098 if !changed {
12099 reindented_line.clear();
12100 continue;
12101 }
12102 // Append the rest of the line and replace old reference with new one
12103 reindented_line.extend(chars);
12104 *line = Cow::Owned(reindented_line.clone());
12105 reindented_line.clear();
12106 }
12107 });
12108 }
12109
12110 pub fn convert_indentation_to_tabs(
12111 &mut self,
12112 _: &ConvertIndentationToTabs,
12113 window: &mut Window,
12114 cx: &mut Context<Self>,
12115 ) {
12116 let settings = self.buffer.read(cx).language_settings(cx);
12117 let tab_size = settings.tab_size.get() as usize;
12118
12119 self.manipulate_mutable_lines(window, cx, |lines| {
12120 // Allocates a reasonably sized buffer once for the whole loop
12121 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12122 // Avoids recomputing spaces that could be inserted many times
12123 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12124 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12125 .collect();
12126
12127 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12128 let mut chars = line.chars();
12129 let mut spaces_count = 0;
12130 let mut first_non_indent_char = None;
12131 let mut changed = false;
12132
12133 for ch in chars.by_ref() {
12134 match ch {
12135 ' ' => {
12136 // Keep track of spaces. Append \t when we reach tab_size
12137 spaces_count += 1;
12138 changed = true;
12139 if spaces_count == tab_size {
12140 reindented_line.push('\t');
12141 spaces_count = 0;
12142 }
12143 }
12144 '\t' => {
12145 reindented_line.push('\t');
12146 spaces_count = 0;
12147 }
12148 _ => {
12149 // Dont append it yet, we might have remaining spaces
12150 first_non_indent_char = Some(ch);
12151 break;
12152 }
12153 }
12154 }
12155
12156 if !changed {
12157 reindented_line.clear();
12158 continue;
12159 }
12160 // Remaining spaces that didn't make a full tab stop
12161 if spaces_count > 0 {
12162 reindented_line.extend(&space_cache[spaces_count - 1]);
12163 }
12164 // If we consume an extra character that was not indentation, add it back
12165 if let Some(extra_char) = first_non_indent_char {
12166 reindented_line.push(extra_char);
12167 }
12168 // Append the rest of the line and replace old reference with new one
12169 reindented_line.extend(chars);
12170 *line = Cow::Owned(reindented_line.clone());
12171 reindented_line.clear();
12172 }
12173 });
12174 }
12175
12176 pub fn convert_to_upper_case(
12177 &mut self,
12178 _: &ConvertToUpperCase,
12179 window: &mut Window,
12180 cx: &mut Context<Self>,
12181 ) {
12182 self.manipulate_text(window, cx, |text| text.to_uppercase())
12183 }
12184
12185 pub fn convert_to_lower_case(
12186 &mut self,
12187 _: &ConvertToLowerCase,
12188 window: &mut Window,
12189 cx: &mut Context<Self>,
12190 ) {
12191 self.manipulate_text(window, cx, |text| text.to_lowercase())
12192 }
12193
12194 pub fn convert_to_title_case(
12195 &mut self,
12196 _: &ConvertToTitleCase,
12197 window: &mut Window,
12198 cx: &mut Context<Self>,
12199 ) {
12200 self.manipulate_text(window, cx, |text| {
12201 text.split('\n')
12202 .map(|line| line.to_case(Case::Title))
12203 .join("\n")
12204 })
12205 }
12206
12207 pub fn convert_to_snake_case(
12208 &mut self,
12209 _: &ConvertToSnakeCase,
12210 window: &mut Window,
12211 cx: &mut Context<Self>,
12212 ) {
12213 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12214 }
12215
12216 pub fn convert_to_kebab_case(
12217 &mut self,
12218 _: &ConvertToKebabCase,
12219 window: &mut Window,
12220 cx: &mut Context<Self>,
12221 ) {
12222 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12223 }
12224
12225 pub fn convert_to_upper_camel_case(
12226 &mut self,
12227 _: &ConvertToUpperCamelCase,
12228 window: &mut Window,
12229 cx: &mut Context<Self>,
12230 ) {
12231 self.manipulate_text(window, cx, |text| {
12232 text.split('\n')
12233 .map(|line| line.to_case(Case::UpperCamel))
12234 .join("\n")
12235 })
12236 }
12237
12238 pub fn convert_to_lower_camel_case(
12239 &mut self,
12240 _: &ConvertToLowerCamelCase,
12241 window: &mut Window,
12242 cx: &mut Context<Self>,
12243 ) {
12244 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12245 }
12246
12247 pub fn convert_to_opposite_case(
12248 &mut self,
12249 _: &ConvertToOppositeCase,
12250 window: &mut Window,
12251 cx: &mut Context<Self>,
12252 ) {
12253 self.manipulate_text(window, cx, |text| {
12254 text.chars()
12255 .fold(String::with_capacity(text.len()), |mut t, c| {
12256 if c.is_uppercase() {
12257 t.extend(c.to_lowercase());
12258 } else {
12259 t.extend(c.to_uppercase());
12260 }
12261 t
12262 })
12263 })
12264 }
12265
12266 pub fn convert_to_sentence_case(
12267 &mut self,
12268 _: &ConvertToSentenceCase,
12269 window: &mut Window,
12270 cx: &mut Context<Self>,
12271 ) {
12272 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12273 }
12274
12275 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12276 self.manipulate_text(window, cx, |text| {
12277 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12278 if has_upper_case_characters {
12279 text.to_lowercase()
12280 } else {
12281 text.to_uppercase()
12282 }
12283 })
12284 }
12285
12286 pub fn convert_to_rot13(
12287 &mut self,
12288 _: &ConvertToRot13,
12289 window: &mut Window,
12290 cx: &mut Context<Self>,
12291 ) {
12292 self.manipulate_text(window, cx, |text| {
12293 text.chars()
12294 .map(|c| match c {
12295 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12296 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12297 _ => c,
12298 })
12299 .collect()
12300 })
12301 }
12302
12303 pub fn convert_to_rot47(
12304 &mut self,
12305 _: &ConvertToRot47,
12306 window: &mut Window,
12307 cx: &mut Context<Self>,
12308 ) {
12309 self.manipulate_text(window, cx, |text| {
12310 text.chars()
12311 .map(|c| {
12312 let code_point = c as u32;
12313 if code_point >= 33 && code_point <= 126 {
12314 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12315 }
12316 c
12317 })
12318 .collect()
12319 })
12320 }
12321
12322 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12323 where
12324 Fn: FnMut(&str) -> String,
12325 {
12326 let buffer = self.buffer.read(cx).snapshot(cx);
12327
12328 let mut new_selections = Vec::new();
12329 let mut edits = Vec::new();
12330 let mut selection_adjustment = 0isize;
12331
12332 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12333 let selection_is_empty = selection.is_empty();
12334
12335 let (start, end) = if selection_is_empty {
12336 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12337 (word_range.start, word_range.end)
12338 } else {
12339 (
12340 buffer.point_to_offset(selection.start),
12341 buffer.point_to_offset(selection.end),
12342 )
12343 };
12344
12345 let text = buffer.text_for_range(start..end).collect::<String>();
12346 let old_length = text.len() as isize;
12347 let text = callback(&text);
12348
12349 new_selections.push(Selection {
12350 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12351 end: MultiBufferOffset(
12352 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12353 ),
12354 goal: SelectionGoal::None,
12355 id: selection.id,
12356 reversed: selection.reversed,
12357 });
12358
12359 selection_adjustment += old_length - text.len() as isize;
12360
12361 edits.push((start..end, text));
12362 }
12363
12364 self.transact(window, cx, |this, window, cx| {
12365 this.buffer.update(cx, |buffer, cx| {
12366 buffer.edit(edits, None, cx);
12367 });
12368
12369 this.change_selections(Default::default(), window, cx, |s| {
12370 s.select(new_selections);
12371 });
12372
12373 this.request_autoscroll(Autoscroll::fit(), cx);
12374 });
12375 }
12376
12377 pub fn move_selection_on_drop(
12378 &mut self,
12379 selection: &Selection<Anchor>,
12380 target: DisplayPoint,
12381 is_cut: bool,
12382 window: &mut Window,
12383 cx: &mut Context<Self>,
12384 ) {
12385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12386 let buffer = display_map.buffer_snapshot();
12387 let mut edits = Vec::new();
12388 let insert_point = display_map
12389 .clip_point(target, Bias::Left)
12390 .to_point(&display_map);
12391 let text = buffer
12392 .text_for_range(selection.start..selection.end)
12393 .collect::<String>();
12394 if is_cut {
12395 edits.push(((selection.start..selection.end), String::new()));
12396 }
12397 let insert_anchor = buffer.anchor_before(insert_point);
12398 edits.push(((insert_anchor..insert_anchor), text));
12399 let last_edit_start = insert_anchor.bias_left(buffer);
12400 let last_edit_end = insert_anchor.bias_right(buffer);
12401 self.transact(window, cx, |this, window, cx| {
12402 this.buffer.update(cx, |buffer, cx| {
12403 buffer.edit(edits, None, cx);
12404 });
12405 this.change_selections(Default::default(), window, cx, |s| {
12406 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12407 });
12408 });
12409 }
12410
12411 pub fn clear_selection_drag_state(&mut self) {
12412 self.selection_drag_state = SelectionDragState::None;
12413 }
12414
12415 pub fn duplicate(
12416 &mut self,
12417 upwards: bool,
12418 whole_lines: bool,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) {
12422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12423
12424 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12425 let buffer = display_map.buffer_snapshot();
12426 let selections = self.selections.all::<Point>(&display_map);
12427
12428 let mut edits = Vec::new();
12429 let mut selections_iter = selections.iter().peekable();
12430 while let Some(selection) = selections_iter.next() {
12431 let mut rows = selection.spanned_rows(false, &display_map);
12432 // duplicate line-wise
12433 if whole_lines || selection.start == selection.end {
12434 // Avoid duplicating the same lines twice.
12435 while let Some(next_selection) = selections_iter.peek() {
12436 let next_rows = next_selection.spanned_rows(false, &display_map);
12437 if next_rows.start < rows.end {
12438 rows.end = next_rows.end;
12439 selections_iter.next().unwrap();
12440 } else {
12441 break;
12442 }
12443 }
12444
12445 // Copy the text from the selected row region and splice it either at the start
12446 // or end of the region.
12447 let start = Point::new(rows.start.0, 0);
12448 let end = Point::new(
12449 rows.end.previous_row().0,
12450 buffer.line_len(rows.end.previous_row()),
12451 );
12452
12453 let mut text = buffer.text_for_range(start..end).collect::<String>();
12454
12455 let insert_location = if upwards {
12456 // When duplicating upward, we need to insert before the current line.
12457 // If we're on the last line and it doesn't end with a newline,
12458 // we need to add a newline before the duplicated content.
12459 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12460 && buffer.max_point().column > 0
12461 && !text.ends_with('\n');
12462
12463 if needs_leading_newline {
12464 text.insert(0, '\n');
12465 end
12466 } else {
12467 text.push('\n');
12468 Point::new(rows.start.0, 0)
12469 }
12470 } else {
12471 text.push('\n');
12472 start
12473 };
12474 edits.push((insert_location..insert_location, text));
12475 } else {
12476 // duplicate character-wise
12477 let start = selection.start;
12478 let end = selection.end;
12479 let text = buffer.text_for_range(start..end).collect::<String>();
12480 edits.push((selection.end..selection.end, text));
12481 }
12482 }
12483
12484 self.transact(window, cx, |this, window, cx| {
12485 this.buffer.update(cx, |buffer, cx| {
12486 buffer.edit(edits, None, cx);
12487 });
12488
12489 // When duplicating upward with whole lines, move the cursor to the duplicated line
12490 if upwards && whole_lines {
12491 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12492
12493 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12494 let mut new_ranges = Vec::new();
12495 let selections = s.all::<Point>(&display_map);
12496 let mut selections_iter = selections.iter().peekable();
12497
12498 while let Some(first_selection) = selections_iter.next() {
12499 // Group contiguous selections together to find the total row span
12500 let mut group_selections = vec![first_selection];
12501 let mut rows = first_selection.spanned_rows(false, &display_map);
12502
12503 while let Some(next_selection) = selections_iter.peek() {
12504 let next_rows = next_selection.spanned_rows(false, &display_map);
12505 if next_rows.start < rows.end {
12506 rows.end = next_rows.end;
12507 group_selections.push(selections_iter.next().unwrap());
12508 } else {
12509 break;
12510 }
12511 }
12512
12513 let row_count = rows.end.0 - rows.start.0;
12514
12515 // Move all selections in this group up by the total number of duplicated rows
12516 for selection in group_selections {
12517 let new_start = Point::new(
12518 selection.start.row.saturating_sub(row_count),
12519 selection.start.column,
12520 );
12521
12522 let new_end = Point::new(
12523 selection.end.row.saturating_sub(row_count),
12524 selection.end.column,
12525 );
12526
12527 new_ranges.push(new_start..new_end);
12528 }
12529 }
12530
12531 s.select_ranges(new_ranges);
12532 });
12533 }
12534
12535 this.request_autoscroll(Autoscroll::fit(), cx);
12536 });
12537 }
12538
12539 pub fn duplicate_line_up(
12540 &mut self,
12541 _: &DuplicateLineUp,
12542 window: &mut Window,
12543 cx: &mut Context<Self>,
12544 ) {
12545 self.duplicate(true, true, window, cx);
12546 }
12547
12548 pub fn duplicate_line_down(
12549 &mut self,
12550 _: &DuplicateLineDown,
12551 window: &mut Window,
12552 cx: &mut Context<Self>,
12553 ) {
12554 self.duplicate(false, true, window, cx);
12555 }
12556
12557 pub fn duplicate_selection(
12558 &mut self,
12559 _: &DuplicateSelection,
12560 window: &mut Window,
12561 cx: &mut Context<Self>,
12562 ) {
12563 self.duplicate(false, false, window, cx);
12564 }
12565
12566 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12568 if self.mode.is_single_line() {
12569 cx.propagate();
12570 return;
12571 }
12572
12573 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12574 let buffer = self.buffer.read(cx).snapshot(cx);
12575
12576 let mut edits = Vec::new();
12577 let mut unfold_ranges = Vec::new();
12578 let mut refold_creases = Vec::new();
12579
12580 let selections = self.selections.all::<Point>(&display_map);
12581 let mut selections = selections.iter().peekable();
12582 let mut contiguous_row_selections = Vec::new();
12583 let mut new_selections = Vec::new();
12584
12585 while let Some(selection) = selections.next() {
12586 // Find all the selections that span a contiguous row range
12587 let (start_row, end_row) = consume_contiguous_rows(
12588 &mut contiguous_row_selections,
12589 selection,
12590 &display_map,
12591 &mut selections,
12592 );
12593
12594 // Move the text spanned by the row range to be before the line preceding the row range
12595 if start_row.0 > 0 {
12596 let range_to_move = Point::new(
12597 start_row.previous_row().0,
12598 buffer.line_len(start_row.previous_row()),
12599 )
12600 ..Point::new(
12601 end_row.previous_row().0,
12602 buffer.line_len(end_row.previous_row()),
12603 );
12604 let insertion_point = display_map
12605 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12606 .0;
12607
12608 // Don't move lines across excerpts
12609 if buffer
12610 .excerpt_containing(insertion_point..range_to_move.end)
12611 .is_some()
12612 {
12613 let text = buffer
12614 .text_for_range(range_to_move.clone())
12615 .flat_map(|s| s.chars())
12616 .skip(1)
12617 .chain(['\n'])
12618 .collect::<String>();
12619
12620 edits.push((
12621 buffer.anchor_after(range_to_move.start)
12622 ..buffer.anchor_before(range_to_move.end),
12623 String::new(),
12624 ));
12625 let insertion_anchor = buffer.anchor_after(insertion_point);
12626 edits.push((insertion_anchor..insertion_anchor, text));
12627
12628 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12629
12630 // Move selections up
12631 new_selections.extend(contiguous_row_selections.drain(..).map(
12632 |mut selection| {
12633 selection.start.row -= row_delta;
12634 selection.end.row -= row_delta;
12635 selection
12636 },
12637 ));
12638
12639 // Move folds up
12640 unfold_ranges.push(range_to_move.clone());
12641 for fold in display_map.folds_in_range(
12642 buffer.anchor_before(range_to_move.start)
12643 ..buffer.anchor_after(range_to_move.end),
12644 ) {
12645 let mut start = fold.range.start.to_point(&buffer);
12646 let mut end = fold.range.end.to_point(&buffer);
12647 start.row -= row_delta;
12648 end.row -= row_delta;
12649 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12650 }
12651 }
12652 }
12653
12654 // If we didn't move line(s), preserve the existing selections
12655 new_selections.append(&mut contiguous_row_selections);
12656 }
12657
12658 self.transact(window, cx, |this, window, cx| {
12659 this.unfold_ranges(&unfold_ranges, true, true, cx);
12660 this.buffer.update(cx, |buffer, cx| {
12661 for (range, text) in edits {
12662 buffer.edit([(range, text)], None, cx);
12663 }
12664 });
12665 this.fold_creases(refold_creases, true, window, cx);
12666 this.change_selections(Default::default(), window, cx, |s| {
12667 s.select(new_selections);
12668 })
12669 });
12670 }
12671
12672 pub fn move_line_down(
12673 &mut self,
12674 _: &MoveLineDown,
12675 window: &mut Window,
12676 cx: &mut Context<Self>,
12677 ) {
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12679 if self.mode.is_single_line() {
12680 cx.propagate();
12681 return;
12682 }
12683
12684 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12685 let buffer = self.buffer.read(cx).snapshot(cx);
12686
12687 let mut edits = Vec::new();
12688 let mut unfold_ranges = Vec::new();
12689 let mut refold_creases = Vec::new();
12690
12691 let selections = self.selections.all::<Point>(&display_map);
12692 let mut selections = selections.iter().peekable();
12693 let mut contiguous_row_selections = Vec::new();
12694 let mut new_selections = Vec::new();
12695
12696 while let Some(selection) = selections.next() {
12697 // Find all the selections that span a contiguous row range
12698 let (start_row, end_row) = consume_contiguous_rows(
12699 &mut contiguous_row_selections,
12700 selection,
12701 &display_map,
12702 &mut selections,
12703 );
12704
12705 // Move the text spanned by the row range to be after the last line of the row range
12706 if end_row.0 <= buffer.max_point().row {
12707 let range_to_move =
12708 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12709 let insertion_point = display_map
12710 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12711 .0;
12712
12713 // Don't move lines across excerpt boundaries
12714 if buffer
12715 .excerpt_containing(range_to_move.start..insertion_point)
12716 .is_some()
12717 {
12718 let mut text = String::from("\n");
12719 text.extend(buffer.text_for_range(range_to_move.clone()));
12720 text.pop(); // Drop trailing newline
12721 edits.push((
12722 buffer.anchor_after(range_to_move.start)
12723 ..buffer.anchor_before(range_to_move.end),
12724 String::new(),
12725 ));
12726 let insertion_anchor = buffer.anchor_after(insertion_point);
12727 edits.push((insertion_anchor..insertion_anchor, text));
12728
12729 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12730
12731 // Move selections down
12732 new_selections.extend(contiguous_row_selections.drain(..).map(
12733 |mut selection| {
12734 selection.start.row += row_delta;
12735 selection.end.row += row_delta;
12736 selection
12737 },
12738 ));
12739
12740 // Move folds down
12741 unfold_ranges.push(range_to_move.clone());
12742 for fold in display_map.folds_in_range(
12743 buffer.anchor_before(range_to_move.start)
12744 ..buffer.anchor_after(range_to_move.end),
12745 ) {
12746 let mut start = fold.range.start.to_point(&buffer);
12747 let mut end = fold.range.end.to_point(&buffer);
12748 start.row += row_delta;
12749 end.row += row_delta;
12750 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12751 }
12752 }
12753 }
12754
12755 // If we didn't move line(s), preserve the existing selections
12756 new_selections.append(&mut contiguous_row_selections);
12757 }
12758
12759 self.transact(window, cx, |this, window, cx| {
12760 this.unfold_ranges(&unfold_ranges, true, true, cx);
12761 this.buffer.update(cx, |buffer, cx| {
12762 for (range, text) in edits {
12763 buffer.edit([(range, text)], None, cx);
12764 }
12765 });
12766 this.fold_creases(refold_creases, true, window, cx);
12767 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12768 });
12769 }
12770
12771 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12773 let text_layout_details = &self.text_layout_details(window);
12774 self.transact(window, cx, |this, window, cx| {
12775 let edits = this.change_selections(Default::default(), window, cx, |s| {
12776 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12777 s.move_with(|display_map, selection| {
12778 if !selection.is_empty() {
12779 return;
12780 }
12781
12782 let mut head = selection.head();
12783 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12784 if head.column() == display_map.line_len(head.row()) {
12785 transpose_offset = display_map
12786 .buffer_snapshot()
12787 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12788 }
12789
12790 if transpose_offset == MultiBufferOffset(0) {
12791 return;
12792 }
12793
12794 *head.column_mut() += 1;
12795 head = display_map.clip_point(head, Bias::Right);
12796 let goal = SelectionGoal::HorizontalPosition(
12797 display_map
12798 .x_for_display_point(head, text_layout_details)
12799 .into(),
12800 );
12801 selection.collapse_to(head, goal);
12802
12803 let transpose_start = display_map
12804 .buffer_snapshot()
12805 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12806 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12807 let transpose_end = display_map
12808 .buffer_snapshot()
12809 .clip_offset(transpose_offset + 1usize, Bias::Right);
12810 if let Some(ch) = display_map
12811 .buffer_snapshot()
12812 .chars_at(transpose_start)
12813 .next()
12814 {
12815 edits.push((transpose_start..transpose_offset, String::new()));
12816 edits.push((transpose_end..transpose_end, ch.to_string()));
12817 }
12818 }
12819 });
12820 edits
12821 });
12822 this.buffer
12823 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12824 let selections = this
12825 .selections
12826 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12827 this.change_selections(Default::default(), window, cx, |s| {
12828 s.select(selections);
12829 });
12830 });
12831 }
12832
12833 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12835 if self.mode.is_single_line() {
12836 cx.propagate();
12837 return;
12838 }
12839
12840 self.rewrap_impl(RewrapOptions::default(), cx)
12841 }
12842
12843 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12844 let buffer = self.buffer.read(cx).snapshot(cx);
12845 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12846
12847 #[derive(Clone, Debug, PartialEq)]
12848 enum CommentFormat {
12849 /// single line comment, with prefix for line
12850 Line(String),
12851 /// single line within a block comment, with prefix for line
12852 BlockLine(String),
12853 /// a single line of a block comment that includes the initial delimiter
12854 BlockCommentWithStart(BlockCommentConfig),
12855 /// a single line of a block comment that includes the ending delimiter
12856 BlockCommentWithEnd(BlockCommentConfig),
12857 }
12858
12859 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12860 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12861 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12862 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12863 .peekable();
12864
12865 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12866 row
12867 } else {
12868 return Vec::new();
12869 };
12870
12871 let language_settings = buffer.language_settings_at(selection.head(), cx);
12872 let language_scope = buffer.language_scope_at(selection.head());
12873
12874 let indent_and_prefix_for_row =
12875 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12876 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12877 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12878 &language_scope
12879 {
12880 let indent_end = Point::new(row, indent.len);
12881 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12882 let line_text_after_indent = buffer
12883 .text_for_range(indent_end..line_end)
12884 .collect::<String>();
12885
12886 let is_within_comment_override = buffer
12887 .language_scope_at(indent_end)
12888 .is_some_and(|scope| scope.override_name() == Some("comment"));
12889 let comment_delimiters = if is_within_comment_override {
12890 // we are within a comment syntax node, but we don't
12891 // yet know what kind of comment: block, doc or line
12892 match (
12893 language_scope.documentation_comment(),
12894 language_scope.block_comment(),
12895 ) {
12896 (Some(config), _) | (_, Some(config))
12897 if buffer.contains_str_at(indent_end, &config.start) =>
12898 {
12899 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12900 }
12901 (Some(config), _) | (_, Some(config))
12902 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12903 {
12904 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12905 }
12906 (Some(config), _) | (_, Some(config))
12907 if buffer.contains_str_at(indent_end, &config.prefix) =>
12908 {
12909 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12910 }
12911 (_, _) => language_scope
12912 .line_comment_prefixes()
12913 .iter()
12914 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12915 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12916 }
12917 } else {
12918 // we not in an overridden comment node, but we may
12919 // be within a non-overridden line comment node
12920 language_scope
12921 .line_comment_prefixes()
12922 .iter()
12923 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12924 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12925 };
12926
12927 let rewrap_prefix = language_scope
12928 .rewrap_prefixes()
12929 .iter()
12930 .find_map(|prefix_regex| {
12931 prefix_regex.find(&line_text_after_indent).map(|mat| {
12932 if mat.start() == 0 {
12933 Some(mat.as_str().to_string())
12934 } else {
12935 None
12936 }
12937 })
12938 })
12939 .flatten();
12940 (comment_delimiters, rewrap_prefix)
12941 } else {
12942 (None, None)
12943 };
12944 (indent, comment_prefix, rewrap_prefix)
12945 };
12946
12947 let mut ranges = Vec::new();
12948 let from_empty_selection = selection.is_empty();
12949
12950 let mut current_range_start = first_row;
12951 let mut prev_row = first_row;
12952 let (
12953 mut current_range_indent,
12954 mut current_range_comment_delimiters,
12955 mut current_range_rewrap_prefix,
12956 ) = indent_and_prefix_for_row(first_row);
12957
12958 for row in non_blank_rows_iter.skip(1) {
12959 let has_paragraph_break = row > prev_row + 1;
12960
12961 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12962 indent_and_prefix_for_row(row);
12963
12964 let has_indent_change = row_indent != current_range_indent;
12965 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12966
12967 let has_boundary_change = has_comment_change
12968 || row_rewrap_prefix.is_some()
12969 || (has_indent_change && current_range_comment_delimiters.is_some());
12970
12971 if has_paragraph_break || has_boundary_change {
12972 ranges.push((
12973 language_settings.clone(),
12974 Point::new(current_range_start, 0)
12975 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12976 current_range_indent,
12977 current_range_comment_delimiters.clone(),
12978 current_range_rewrap_prefix.clone(),
12979 from_empty_selection,
12980 ));
12981 current_range_start = row;
12982 current_range_indent = row_indent;
12983 current_range_comment_delimiters = row_comment_delimiters;
12984 current_range_rewrap_prefix = row_rewrap_prefix;
12985 }
12986 prev_row = row;
12987 }
12988
12989 ranges.push((
12990 language_settings.clone(),
12991 Point::new(current_range_start, 0)
12992 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12993 current_range_indent,
12994 current_range_comment_delimiters,
12995 current_range_rewrap_prefix,
12996 from_empty_selection,
12997 ));
12998
12999 ranges
13000 });
13001
13002 let mut edits = Vec::new();
13003 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13004
13005 for (
13006 language_settings,
13007 wrap_range,
13008 mut indent_size,
13009 comment_prefix,
13010 rewrap_prefix,
13011 from_empty_selection,
13012 ) in wrap_ranges
13013 {
13014 let mut start_row = wrap_range.start.row;
13015 let mut end_row = wrap_range.end.row;
13016
13017 // Skip selections that overlap with a range that has already been rewrapped.
13018 let selection_range = start_row..end_row;
13019 if rewrapped_row_ranges
13020 .iter()
13021 .any(|range| range.overlaps(&selection_range))
13022 {
13023 continue;
13024 }
13025
13026 let tab_size = language_settings.tab_size;
13027
13028 let (line_prefix, inside_comment) = match &comment_prefix {
13029 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13030 (Some(prefix.as_str()), true)
13031 }
13032 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13033 (Some(prefix.as_ref()), true)
13034 }
13035 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13036 start: _,
13037 end: _,
13038 prefix,
13039 tab_size,
13040 })) => {
13041 indent_size.len += tab_size;
13042 (Some(prefix.as_ref()), true)
13043 }
13044 None => (None, false),
13045 };
13046 let indent_prefix = indent_size.chars().collect::<String>();
13047 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13048
13049 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13050 RewrapBehavior::InComments => inside_comment,
13051 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13052 RewrapBehavior::Anywhere => true,
13053 };
13054
13055 let should_rewrap = options.override_language_settings
13056 || allow_rewrap_based_on_language
13057 || self.hard_wrap.is_some();
13058 if !should_rewrap {
13059 continue;
13060 }
13061
13062 if from_empty_selection {
13063 'expand_upwards: while start_row > 0 {
13064 let prev_row = start_row - 1;
13065 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13066 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13067 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13068 {
13069 start_row = prev_row;
13070 } else {
13071 break 'expand_upwards;
13072 }
13073 }
13074
13075 'expand_downwards: while end_row < buffer.max_point().row {
13076 let next_row = end_row + 1;
13077 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13078 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13079 && !buffer.is_line_blank(MultiBufferRow(next_row))
13080 {
13081 end_row = next_row;
13082 } else {
13083 break 'expand_downwards;
13084 }
13085 }
13086 }
13087
13088 let start = Point::new(start_row, 0);
13089 let start_offset = ToOffset::to_offset(&start, &buffer);
13090 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13091 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13092 let mut first_line_delimiter = None;
13093 let mut last_line_delimiter = None;
13094 let Some(lines_without_prefixes) = selection_text
13095 .lines()
13096 .enumerate()
13097 .map(|(ix, line)| {
13098 let line_trimmed = line.trim_start();
13099 if rewrap_prefix.is_some() && ix > 0 {
13100 Ok(line_trimmed)
13101 } else if let Some(
13102 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13103 start,
13104 prefix,
13105 end,
13106 tab_size,
13107 })
13108 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13109 start,
13110 prefix,
13111 end,
13112 tab_size,
13113 }),
13114 ) = &comment_prefix
13115 {
13116 let line_trimmed = line_trimmed
13117 .strip_prefix(start.as_ref())
13118 .map(|s| {
13119 let mut indent_size = indent_size;
13120 indent_size.len -= tab_size;
13121 let indent_prefix: String = indent_size.chars().collect();
13122 first_line_delimiter = Some((indent_prefix, start));
13123 s.trim_start()
13124 })
13125 .unwrap_or(line_trimmed);
13126 let line_trimmed = line_trimmed
13127 .strip_suffix(end.as_ref())
13128 .map(|s| {
13129 last_line_delimiter = Some(end);
13130 s.trim_end()
13131 })
13132 .unwrap_or(line_trimmed);
13133 let line_trimmed = line_trimmed
13134 .strip_prefix(prefix.as_ref())
13135 .unwrap_or(line_trimmed);
13136 Ok(line_trimmed)
13137 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13138 line_trimmed.strip_prefix(prefix).with_context(|| {
13139 format!("line did not start with prefix {prefix:?}: {line:?}")
13140 })
13141 } else {
13142 line_trimmed
13143 .strip_prefix(&line_prefix.trim_start())
13144 .with_context(|| {
13145 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13146 })
13147 }
13148 })
13149 .collect::<Result<Vec<_>, _>>()
13150 .log_err()
13151 else {
13152 continue;
13153 };
13154
13155 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13156 buffer
13157 .language_settings_at(Point::new(start_row, 0), cx)
13158 .preferred_line_length as usize
13159 });
13160
13161 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13162 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13163 } else {
13164 line_prefix.clone()
13165 };
13166
13167 let wrapped_text = {
13168 let mut wrapped_text = wrap_with_prefix(
13169 line_prefix,
13170 subsequent_lines_prefix,
13171 lines_without_prefixes.join("\n"),
13172 wrap_column,
13173 tab_size,
13174 options.preserve_existing_whitespace,
13175 );
13176
13177 if let Some((indent, delimiter)) = first_line_delimiter {
13178 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13179 }
13180 if let Some(last_line) = last_line_delimiter {
13181 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13182 }
13183
13184 wrapped_text
13185 };
13186
13187 // TODO: should always use char-based diff while still supporting cursor behavior that
13188 // matches vim.
13189 let mut diff_options = DiffOptions::default();
13190 if options.override_language_settings {
13191 diff_options.max_word_diff_len = 0;
13192 diff_options.max_word_diff_line_count = 0;
13193 } else {
13194 diff_options.max_word_diff_len = usize::MAX;
13195 diff_options.max_word_diff_line_count = usize::MAX;
13196 }
13197
13198 for (old_range, new_text) in
13199 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13200 {
13201 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13202 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13203 edits.push((edit_start..edit_end, new_text));
13204 }
13205
13206 rewrapped_row_ranges.push(start_row..=end_row);
13207 }
13208
13209 self.buffer
13210 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13211 }
13212
13213 pub fn cut_common(
13214 &mut self,
13215 cut_no_selection_line: bool,
13216 window: &mut Window,
13217 cx: &mut Context<Self>,
13218 ) -> ClipboardItem {
13219 let mut text = String::new();
13220 let buffer = self.buffer.read(cx).snapshot(cx);
13221 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13222 let mut clipboard_selections = Vec::with_capacity(selections.len());
13223 {
13224 let max_point = buffer.max_point();
13225 let mut is_first = true;
13226 let mut prev_selection_was_entire_line = false;
13227 for selection in &mut selections {
13228 let is_entire_line =
13229 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13230 if is_entire_line {
13231 selection.start = Point::new(selection.start.row, 0);
13232 if !selection.is_empty() && selection.end.column == 0 {
13233 selection.end = cmp::min(max_point, selection.end);
13234 } else {
13235 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13236 }
13237 selection.goal = SelectionGoal::None;
13238 }
13239 if is_first {
13240 is_first = false;
13241 } else if !prev_selection_was_entire_line {
13242 text += "\n";
13243 }
13244 prev_selection_was_entire_line = is_entire_line;
13245 let mut len = 0;
13246 for chunk in buffer.text_for_range(selection.start..selection.end) {
13247 text.push_str(chunk);
13248 len += chunk.len();
13249 }
13250
13251 clipboard_selections.push(ClipboardSelection::for_buffer(
13252 len,
13253 is_entire_line,
13254 selection.range(),
13255 &buffer,
13256 self.project.as_ref(),
13257 cx,
13258 ));
13259 }
13260 }
13261
13262 self.transact(window, cx, |this, window, cx| {
13263 this.change_selections(Default::default(), window, cx, |s| {
13264 s.select(selections);
13265 });
13266 this.insert("", window, cx);
13267 });
13268 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13269 }
13270
13271 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13273 let item = self.cut_common(true, window, cx);
13274 cx.write_to_clipboard(item);
13275 }
13276
13277 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13278 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13279 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13280 s.move_with(|snapshot, sel| {
13281 if sel.is_empty() {
13282 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13283 }
13284 if sel.is_empty() {
13285 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13286 }
13287 });
13288 });
13289 let item = self.cut_common(false, window, cx);
13290 cx.set_global(KillRing(item))
13291 }
13292
13293 pub fn kill_ring_yank(
13294 &mut self,
13295 _: &KillRingYank,
13296 window: &mut Window,
13297 cx: &mut Context<Self>,
13298 ) {
13299 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13300 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13301 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13302 (kill_ring.text().to_string(), kill_ring.metadata_json())
13303 } else {
13304 return;
13305 }
13306 } else {
13307 return;
13308 };
13309 self.do_paste(&text, metadata, false, window, cx);
13310 }
13311
13312 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13313 self.do_copy(true, cx);
13314 }
13315
13316 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13317 self.do_copy(false, cx);
13318 }
13319
13320 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13321 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13322 let buffer = self.buffer.read(cx).read(cx);
13323 let mut text = String::new();
13324
13325 let mut clipboard_selections = Vec::with_capacity(selections.len());
13326 {
13327 let max_point = buffer.max_point();
13328 let mut is_first = true;
13329 let mut prev_selection_was_entire_line = false;
13330 for selection in &selections {
13331 let mut start = selection.start;
13332 let mut end = selection.end;
13333 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13334 let mut add_trailing_newline = false;
13335 if is_entire_line {
13336 start = Point::new(start.row, 0);
13337 let next_line_start = Point::new(end.row + 1, 0);
13338 if next_line_start <= max_point {
13339 end = next_line_start;
13340 } else {
13341 // We're on the last line without a trailing newline.
13342 // Copy to the end of the line and add a newline afterwards.
13343 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13344 add_trailing_newline = true;
13345 }
13346 }
13347
13348 let mut trimmed_selections = Vec::new();
13349 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13350 let row = MultiBufferRow(start.row);
13351 let first_indent = buffer.indent_size_for_line(row);
13352 if first_indent.len == 0 || start.column > first_indent.len {
13353 trimmed_selections.push(start..end);
13354 } else {
13355 trimmed_selections.push(
13356 Point::new(row.0, first_indent.len)
13357 ..Point::new(row.0, buffer.line_len(row)),
13358 );
13359 for row in start.row + 1..=end.row {
13360 let mut line_len = buffer.line_len(MultiBufferRow(row));
13361 if row == end.row {
13362 line_len = end.column;
13363 }
13364 if line_len == 0 {
13365 trimmed_selections
13366 .push(Point::new(row, 0)..Point::new(row, line_len));
13367 continue;
13368 }
13369 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13370 if row_indent_size.len >= first_indent.len {
13371 trimmed_selections.push(
13372 Point::new(row, first_indent.len)..Point::new(row, line_len),
13373 );
13374 } else {
13375 trimmed_selections.clear();
13376 trimmed_selections.push(start..end);
13377 break;
13378 }
13379 }
13380 }
13381 } else {
13382 trimmed_selections.push(start..end);
13383 }
13384
13385 let is_multiline_trim = trimmed_selections.len() > 1;
13386 for trimmed_range in trimmed_selections {
13387 if is_first {
13388 is_first = false;
13389 } else if is_multiline_trim || !prev_selection_was_entire_line {
13390 text += "\n";
13391 }
13392 prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13393 let mut len = 0;
13394 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13395 text.push_str(chunk);
13396 len += chunk.len();
13397 }
13398 if add_trailing_newline {
13399 text.push('\n');
13400 len += 1;
13401 }
13402 clipboard_selections.push(ClipboardSelection::for_buffer(
13403 len,
13404 is_entire_line,
13405 trimmed_range,
13406 &buffer,
13407 self.project.as_ref(),
13408 cx,
13409 ));
13410 }
13411 }
13412 }
13413
13414 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13415 text,
13416 clipboard_selections,
13417 ));
13418 }
13419
13420 pub fn do_paste(
13421 &mut self,
13422 text: &String,
13423 clipboard_selections: Option<Vec<ClipboardSelection>>,
13424 handle_entire_lines: bool,
13425 window: &mut Window,
13426 cx: &mut Context<Self>,
13427 ) {
13428 if self.read_only(cx) {
13429 return;
13430 }
13431
13432 let clipboard_text = Cow::Borrowed(text.as_str());
13433
13434 self.transact(window, cx, |this, window, cx| {
13435 let had_active_edit_prediction = this.has_active_edit_prediction();
13436 let display_map = this.display_snapshot(cx);
13437 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13438 let cursor_offset = this
13439 .selections
13440 .last::<MultiBufferOffset>(&display_map)
13441 .head();
13442
13443 if let Some(mut clipboard_selections) = clipboard_selections {
13444 let all_selections_were_entire_line =
13445 clipboard_selections.iter().all(|s| s.is_entire_line);
13446 let first_selection_indent_column =
13447 clipboard_selections.first().map(|s| s.first_line_indent);
13448 if clipboard_selections.len() != old_selections.len() {
13449 clipboard_selections.drain(..);
13450 }
13451 let mut auto_indent_on_paste = true;
13452
13453 this.buffer.update(cx, |buffer, cx| {
13454 let snapshot = buffer.read(cx);
13455 auto_indent_on_paste = snapshot
13456 .language_settings_at(cursor_offset, cx)
13457 .auto_indent_on_paste;
13458
13459 let mut start_offset = 0;
13460 let mut edits = Vec::new();
13461 let mut original_indent_columns = Vec::new();
13462 for (ix, selection) in old_selections.iter().enumerate() {
13463 let to_insert;
13464 let entire_line;
13465 let original_indent_column;
13466 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13467 let end_offset = start_offset + clipboard_selection.len;
13468 to_insert = &clipboard_text[start_offset..end_offset];
13469 entire_line = clipboard_selection.is_entire_line;
13470 start_offset = if entire_line {
13471 end_offset
13472 } else {
13473 end_offset + 1
13474 };
13475 original_indent_column = Some(clipboard_selection.first_line_indent);
13476 } else {
13477 to_insert = &*clipboard_text;
13478 entire_line = all_selections_were_entire_line;
13479 original_indent_column = first_selection_indent_column
13480 }
13481
13482 let (range, to_insert) =
13483 if selection.is_empty() && handle_entire_lines && entire_line {
13484 // If the corresponding selection was empty when this slice of the
13485 // clipboard text was written, then the entire line containing the
13486 // selection was copied. If this selection is also currently empty,
13487 // then paste the line before the current line of the buffer.
13488 let column = selection.start.to_point(&snapshot).column as usize;
13489 let line_start = selection.start - column;
13490 (line_start..line_start, Cow::Borrowed(to_insert))
13491 } else {
13492 let language = snapshot.language_at(selection.head());
13493 let range = selection.range();
13494 if let Some(language) = language
13495 && language.name() == "Markdown".into()
13496 {
13497 edit_for_markdown_paste(
13498 &snapshot,
13499 range,
13500 to_insert,
13501 url::Url::parse(to_insert).ok(),
13502 )
13503 } else {
13504 (range, Cow::Borrowed(to_insert))
13505 }
13506 };
13507
13508 edits.push((range, to_insert));
13509 original_indent_columns.push(original_indent_column);
13510 }
13511 drop(snapshot);
13512
13513 buffer.edit(
13514 edits,
13515 if auto_indent_on_paste {
13516 Some(AutoindentMode::Block {
13517 original_indent_columns,
13518 })
13519 } else {
13520 None
13521 },
13522 cx,
13523 );
13524 });
13525
13526 let selections = this
13527 .selections
13528 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13529 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13530 } else {
13531 let url = url::Url::parse(&clipboard_text).ok();
13532
13533 let auto_indent_mode = if !clipboard_text.is_empty() {
13534 Some(AutoindentMode::Block {
13535 original_indent_columns: Vec::new(),
13536 })
13537 } else {
13538 None
13539 };
13540
13541 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13542 let snapshot = buffer.snapshot(cx);
13543
13544 let anchors = old_selections
13545 .iter()
13546 .map(|s| {
13547 let anchor = snapshot.anchor_after(s.head());
13548 s.map(|_| anchor)
13549 })
13550 .collect::<Vec<_>>();
13551
13552 let mut edits = Vec::new();
13553
13554 for selection in old_selections.iter() {
13555 let language = snapshot.language_at(selection.head());
13556 let range = selection.range();
13557
13558 let (edit_range, edit_text) = if let Some(language) = language
13559 && language.name() == "Markdown".into()
13560 {
13561 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13562 } else {
13563 (range, clipboard_text.clone())
13564 };
13565
13566 edits.push((edit_range, edit_text));
13567 }
13568
13569 drop(snapshot);
13570 buffer.edit(edits, auto_indent_mode, cx);
13571
13572 anchors
13573 });
13574
13575 this.change_selections(Default::default(), window, cx, |s| {
13576 s.select_anchors(selection_anchors);
13577 });
13578 }
13579
13580 // 🤔 | .. | show_in_menu |
13581 // | .. | true true
13582 // | had_edit_prediction | false true
13583
13584 let trigger_in_words =
13585 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13586
13587 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13588 });
13589 }
13590
13591 pub fn diff_clipboard_with_selection(
13592 &mut self,
13593 _: &DiffClipboardWithSelection,
13594 window: &mut Window,
13595 cx: &mut Context<Self>,
13596 ) {
13597 let selections = self
13598 .selections
13599 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13600
13601 if selections.is_empty() {
13602 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13603 return;
13604 };
13605
13606 let clipboard_text = match cx.read_from_clipboard() {
13607 Some(item) => match item.entries().first() {
13608 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13609 _ => None,
13610 },
13611 None => None,
13612 };
13613
13614 let Some(clipboard_text) = clipboard_text else {
13615 log::warn!("Clipboard doesn't contain text.");
13616 return;
13617 };
13618
13619 window.dispatch_action(
13620 Box::new(DiffClipboardWithSelectionData {
13621 clipboard_text,
13622 editor: cx.entity(),
13623 }),
13624 cx,
13625 );
13626 }
13627
13628 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13630 if let Some(item) = cx.read_from_clipboard() {
13631 let entries = item.entries();
13632
13633 match entries.first() {
13634 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13635 // of all the pasted entries.
13636 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13637 .do_paste(
13638 clipboard_string.text(),
13639 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13640 true,
13641 window,
13642 cx,
13643 ),
13644 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13645 }
13646 }
13647 }
13648
13649 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13650 if self.read_only(cx) {
13651 return;
13652 }
13653
13654 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13655
13656 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13657 if let Some((selections, _)) =
13658 self.selection_history.transaction(transaction_id).cloned()
13659 {
13660 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13661 s.select_anchors(selections.to_vec());
13662 });
13663 } else {
13664 log::error!(
13665 "No entry in selection_history found for undo. \
13666 This may correspond to a bug where undo does not update the selection. \
13667 If this is occurring, please add details to \
13668 https://github.com/zed-industries/zed/issues/22692"
13669 );
13670 }
13671 self.request_autoscroll(Autoscroll::fit(), cx);
13672 self.unmark_text(window, cx);
13673 self.refresh_edit_prediction(true, false, window, cx);
13674 cx.emit(EditorEvent::Edited { transaction_id });
13675 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13676 }
13677 }
13678
13679 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13680 if self.read_only(cx) {
13681 return;
13682 }
13683
13684 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13685
13686 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13687 if let Some((_, Some(selections))) =
13688 self.selection_history.transaction(transaction_id).cloned()
13689 {
13690 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13691 s.select_anchors(selections.to_vec());
13692 });
13693 } else {
13694 log::error!(
13695 "No entry in selection_history found for redo. \
13696 This may correspond to a bug where undo does not update the selection. \
13697 If this is occurring, please add details to \
13698 https://github.com/zed-industries/zed/issues/22692"
13699 );
13700 }
13701 self.request_autoscroll(Autoscroll::fit(), cx);
13702 self.unmark_text(window, cx);
13703 self.refresh_edit_prediction(true, false, window, cx);
13704 cx.emit(EditorEvent::Edited { transaction_id });
13705 }
13706 }
13707
13708 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13709 self.buffer
13710 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13711 }
13712
13713 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13714 self.buffer
13715 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13716 }
13717
13718 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13719 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13720 self.change_selections(Default::default(), window, cx, |s| {
13721 s.move_with(|map, selection| {
13722 let cursor = if selection.is_empty() {
13723 movement::left(map, selection.start)
13724 } else {
13725 selection.start
13726 };
13727 selection.collapse_to(cursor, SelectionGoal::None);
13728 });
13729 })
13730 }
13731
13732 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13734 self.change_selections(Default::default(), window, cx, |s| {
13735 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13736 })
13737 }
13738
13739 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13741 self.change_selections(Default::default(), window, cx, |s| {
13742 s.move_with(|map, selection| {
13743 let cursor = if selection.is_empty() {
13744 movement::right(map, selection.end)
13745 } else {
13746 selection.end
13747 };
13748 selection.collapse_to(cursor, SelectionGoal::None)
13749 });
13750 })
13751 }
13752
13753 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13755 self.change_selections(Default::default(), window, cx, |s| {
13756 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13757 });
13758 }
13759
13760 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13761 if self.take_rename(true, window, cx).is_some() {
13762 return;
13763 }
13764
13765 if self.mode.is_single_line() {
13766 cx.propagate();
13767 return;
13768 }
13769
13770 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13771
13772 let text_layout_details = &self.text_layout_details(window);
13773 let selection_count = self.selections.count();
13774 let first_selection = self.selections.first_anchor();
13775
13776 self.change_selections(Default::default(), window, cx, |s| {
13777 s.move_with(|map, selection| {
13778 if !selection.is_empty() {
13779 selection.goal = SelectionGoal::None;
13780 }
13781 let (cursor, goal) = movement::up(
13782 map,
13783 selection.start,
13784 selection.goal,
13785 false,
13786 text_layout_details,
13787 );
13788 selection.collapse_to(cursor, goal);
13789 });
13790 });
13791
13792 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13793 {
13794 cx.propagate();
13795 }
13796 }
13797
13798 pub fn move_up_by_lines(
13799 &mut self,
13800 action: &MoveUpByLines,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) {
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
13817 self.change_selections(Default::default(), window, cx, |s| {
13818 s.move_with(|map, selection| {
13819 if !selection.is_empty() {
13820 selection.goal = SelectionGoal::None;
13821 }
13822 let (cursor, goal) = movement::up_by_rows(
13823 map,
13824 selection.start,
13825 action.lines,
13826 selection.goal,
13827 false,
13828 text_layout_details,
13829 );
13830 selection.collapse_to(cursor, goal);
13831 });
13832 })
13833 }
13834
13835 pub fn move_down_by_lines(
13836 &mut self,
13837 action: &MoveDownByLines,
13838 window: &mut Window,
13839 cx: &mut Context<Self>,
13840 ) {
13841 if self.take_rename(true, window, cx).is_some() {
13842 return;
13843 }
13844
13845 if self.mode.is_single_line() {
13846 cx.propagate();
13847 return;
13848 }
13849
13850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13851
13852 let text_layout_details = &self.text_layout_details(window);
13853
13854 self.change_selections(Default::default(), window, cx, |s| {
13855 s.move_with(|map, selection| {
13856 if !selection.is_empty() {
13857 selection.goal = SelectionGoal::None;
13858 }
13859 let (cursor, goal) = movement::down_by_rows(
13860 map,
13861 selection.start,
13862 action.lines,
13863 selection.goal,
13864 false,
13865 text_layout_details,
13866 );
13867 selection.collapse_to(cursor, goal);
13868 });
13869 })
13870 }
13871
13872 pub fn select_down_by_lines(
13873 &mut self,
13874 action: &SelectDownByLines,
13875 window: &mut Window,
13876 cx: &mut Context<Self>,
13877 ) {
13878 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13879 let text_layout_details = &self.text_layout_details(window);
13880 self.change_selections(Default::default(), window, cx, |s| {
13881 s.move_heads_with(|map, head, goal| {
13882 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13883 })
13884 })
13885 }
13886
13887 pub fn select_up_by_lines(
13888 &mut self,
13889 action: &SelectUpByLines,
13890 window: &mut Window,
13891 cx: &mut Context<Self>,
13892 ) {
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894 let text_layout_details = &self.text_layout_details(window);
13895 self.change_selections(Default::default(), window, cx, |s| {
13896 s.move_heads_with(|map, head, goal| {
13897 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13898 })
13899 })
13900 }
13901
13902 pub fn select_page_up(
13903 &mut self,
13904 _: &SelectPageUp,
13905 window: &mut Window,
13906 cx: &mut Context<Self>,
13907 ) {
13908 let Some(row_count) = self.visible_row_count() else {
13909 return;
13910 };
13911
13912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13913
13914 let text_layout_details = &self.text_layout_details(window);
13915
13916 self.change_selections(Default::default(), window, cx, |s| {
13917 s.move_heads_with(|map, head, goal| {
13918 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13919 })
13920 })
13921 }
13922
13923 pub fn move_page_up(
13924 &mut self,
13925 action: &MovePageUp,
13926 window: &mut Window,
13927 cx: &mut Context<Self>,
13928 ) {
13929 if self.take_rename(true, window, cx).is_some() {
13930 return;
13931 }
13932
13933 if self
13934 .context_menu
13935 .borrow_mut()
13936 .as_mut()
13937 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13938 .unwrap_or(false)
13939 {
13940 return;
13941 }
13942
13943 if matches!(self.mode, EditorMode::SingleLine) {
13944 cx.propagate();
13945 return;
13946 }
13947
13948 let Some(row_count) = self.visible_row_count() else {
13949 return;
13950 };
13951
13952 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13953
13954 let effects = if action.center_cursor {
13955 SelectionEffects::scroll(Autoscroll::center())
13956 } else {
13957 SelectionEffects::default()
13958 };
13959
13960 let text_layout_details = &self.text_layout_details(window);
13961
13962 self.change_selections(effects, window, cx, |s| {
13963 s.move_with(|map, selection| {
13964 if !selection.is_empty() {
13965 selection.goal = SelectionGoal::None;
13966 }
13967 let (cursor, goal) = movement::up_by_rows(
13968 map,
13969 selection.end,
13970 row_count,
13971 selection.goal,
13972 false,
13973 text_layout_details,
13974 );
13975 selection.collapse_to(cursor, goal);
13976 });
13977 });
13978 }
13979
13980 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13982 let text_layout_details = &self.text_layout_details(window);
13983 self.change_selections(Default::default(), window, cx, |s| {
13984 s.move_heads_with(|map, head, goal| {
13985 movement::up(map, head, goal, false, text_layout_details)
13986 })
13987 })
13988 }
13989
13990 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13991 self.take_rename(true, window, cx);
13992
13993 if self.mode.is_single_line() {
13994 cx.propagate();
13995 return;
13996 }
13997
13998 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13999
14000 let text_layout_details = &self.text_layout_details(window);
14001 let selection_count = self.selections.count();
14002 let first_selection = self.selections.first_anchor();
14003
14004 self.change_selections(Default::default(), window, cx, |s| {
14005 s.move_with(|map, selection| {
14006 if !selection.is_empty() {
14007 selection.goal = SelectionGoal::None;
14008 }
14009 let (cursor, goal) = movement::down(
14010 map,
14011 selection.end,
14012 selection.goal,
14013 false,
14014 text_layout_details,
14015 );
14016 selection.collapse_to(cursor, goal);
14017 });
14018 });
14019
14020 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14021 {
14022 cx.propagate();
14023 }
14024 }
14025
14026 pub fn select_page_down(
14027 &mut self,
14028 _: &SelectPageDown,
14029 window: &mut Window,
14030 cx: &mut Context<Self>,
14031 ) {
14032 let Some(row_count) = self.visible_row_count() else {
14033 return;
14034 };
14035
14036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14037
14038 let text_layout_details = &self.text_layout_details(window);
14039
14040 self.change_selections(Default::default(), window, cx, |s| {
14041 s.move_heads_with(|map, head, goal| {
14042 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14043 })
14044 })
14045 }
14046
14047 pub fn move_page_down(
14048 &mut self,
14049 action: &MovePageDown,
14050 window: &mut Window,
14051 cx: &mut Context<Self>,
14052 ) {
14053 if self.take_rename(true, window, cx).is_some() {
14054 return;
14055 }
14056
14057 if self
14058 .context_menu
14059 .borrow_mut()
14060 .as_mut()
14061 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14062 .unwrap_or(false)
14063 {
14064 return;
14065 }
14066
14067 if matches!(self.mode, EditorMode::SingleLine) {
14068 cx.propagate();
14069 return;
14070 }
14071
14072 let Some(row_count) = self.visible_row_count() else {
14073 return;
14074 };
14075
14076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14077
14078 let effects = if action.center_cursor {
14079 SelectionEffects::scroll(Autoscroll::center())
14080 } else {
14081 SelectionEffects::default()
14082 };
14083
14084 let text_layout_details = &self.text_layout_details(window);
14085 self.change_selections(effects, window, cx, |s| {
14086 s.move_with(|map, selection| {
14087 if !selection.is_empty() {
14088 selection.goal = SelectionGoal::None;
14089 }
14090 let (cursor, goal) = movement::down_by_rows(
14091 map,
14092 selection.end,
14093 row_count,
14094 selection.goal,
14095 false,
14096 text_layout_details,
14097 );
14098 selection.collapse_to(cursor, goal);
14099 });
14100 });
14101 }
14102
14103 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14105 let text_layout_details = &self.text_layout_details(window);
14106 self.change_selections(Default::default(), window, cx, |s| {
14107 s.move_heads_with(|map, head, goal| {
14108 movement::down(map, head, goal, false, text_layout_details)
14109 })
14110 });
14111 }
14112
14113 pub fn context_menu_first(
14114 &mut self,
14115 _: &ContextMenuFirst,
14116 window: &mut Window,
14117 cx: &mut Context<Self>,
14118 ) {
14119 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14120 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14121 }
14122 }
14123
14124 pub fn context_menu_prev(
14125 &mut self,
14126 _: &ContextMenuPrevious,
14127 window: &mut Window,
14128 cx: &mut Context<Self>,
14129 ) {
14130 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14131 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14132 }
14133 }
14134
14135 pub fn context_menu_next(
14136 &mut self,
14137 _: &ContextMenuNext,
14138 window: &mut Window,
14139 cx: &mut Context<Self>,
14140 ) {
14141 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14142 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14143 }
14144 }
14145
14146 pub fn context_menu_last(
14147 &mut self,
14148 _: &ContextMenuLast,
14149 window: &mut Window,
14150 cx: &mut Context<Self>,
14151 ) {
14152 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14153 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14154 }
14155 }
14156
14157 pub fn signature_help_prev(
14158 &mut self,
14159 _: &SignatureHelpPrevious,
14160 _: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) {
14163 if let Some(popover) = self.signature_help_state.popover_mut() {
14164 if popover.current_signature == 0 {
14165 popover.current_signature = popover.signatures.len() - 1;
14166 } else {
14167 popover.current_signature -= 1;
14168 }
14169 cx.notify();
14170 }
14171 }
14172
14173 pub fn signature_help_next(
14174 &mut self,
14175 _: &SignatureHelpNext,
14176 _: &mut Window,
14177 cx: &mut Context<Self>,
14178 ) {
14179 if let Some(popover) = self.signature_help_state.popover_mut() {
14180 if popover.current_signature + 1 == popover.signatures.len() {
14181 popover.current_signature = 0;
14182 } else {
14183 popover.current_signature += 1;
14184 }
14185 cx.notify();
14186 }
14187 }
14188
14189 pub fn move_to_previous_word_start(
14190 &mut self,
14191 _: &MoveToPreviousWordStart,
14192 window: &mut Window,
14193 cx: &mut Context<Self>,
14194 ) {
14195 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14196 self.change_selections(Default::default(), window, cx, |s| {
14197 s.move_cursors_with(|map, head, _| {
14198 (
14199 movement::previous_word_start(map, head),
14200 SelectionGoal::None,
14201 )
14202 });
14203 })
14204 }
14205
14206 pub fn move_to_previous_subword_start(
14207 &mut self,
14208 _: &MoveToPreviousSubwordStart,
14209 window: &mut Window,
14210 cx: &mut Context<Self>,
14211 ) {
14212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14213 self.change_selections(Default::default(), window, cx, |s| {
14214 s.move_cursors_with(|map, head, _| {
14215 (
14216 movement::previous_subword_start(map, head),
14217 SelectionGoal::None,
14218 )
14219 });
14220 })
14221 }
14222
14223 pub fn select_to_previous_word_start(
14224 &mut self,
14225 _: &SelectToPreviousWordStart,
14226 window: &mut Window,
14227 cx: &mut Context<Self>,
14228 ) {
14229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14230 self.change_selections(Default::default(), window, cx, |s| {
14231 s.move_heads_with(|map, head, _| {
14232 (
14233 movement::previous_word_start(map, head),
14234 SelectionGoal::None,
14235 )
14236 });
14237 })
14238 }
14239
14240 pub fn select_to_previous_subword_start(
14241 &mut self,
14242 _: &SelectToPreviousSubwordStart,
14243 window: &mut Window,
14244 cx: &mut Context<Self>,
14245 ) {
14246 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14247 self.change_selections(Default::default(), window, cx, |s| {
14248 s.move_heads_with(|map, head, _| {
14249 (
14250 movement::previous_subword_start(map, head),
14251 SelectionGoal::None,
14252 )
14253 });
14254 })
14255 }
14256
14257 pub fn delete_to_previous_word_start(
14258 &mut self,
14259 action: &DeleteToPreviousWordStart,
14260 window: &mut Window,
14261 cx: &mut Context<Self>,
14262 ) {
14263 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14264 self.transact(window, cx, |this, window, cx| {
14265 this.select_autoclose_pair(window, cx);
14266 this.change_selections(Default::default(), window, cx, |s| {
14267 s.move_with(|map, selection| {
14268 if selection.is_empty() {
14269 let mut cursor = if action.ignore_newlines {
14270 movement::previous_word_start(map, selection.head())
14271 } else {
14272 movement::previous_word_start_or_newline(map, selection.head())
14273 };
14274 cursor = movement::adjust_greedy_deletion(
14275 map,
14276 selection.head(),
14277 cursor,
14278 action.ignore_brackets,
14279 );
14280 selection.set_head(cursor, SelectionGoal::None);
14281 }
14282 });
14283 });
14284 this.insert("", window, cx);
14285 });
14286 }
14287
14288 pub fn delete_to_previous_subword_start(
14289 &mut self,
14290 action: &DeleteToPreviousSubwordStart,
14291 window: &mut Window,
14292 cx: &mut Context<Self>,
14293 ) {
14294 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14295 self.transact(window, cx, |this, window, cx| {
14296 this.select_autoclose_pair(window, cx);
14297 this.change_selections(Default::default(), window, cx, |s| {
14298 s.move_with(|map, selection| {
14299 if selection.is_empty() {
14300 let mut cursor = if action.ignore_newlines {
14301 movement::previous_subword_start(map, selection.head())
14302 } else {
14303 movement::previous_subword_start_or_newline(map, selection.head())
14304 };
14305 cursor = movement::adjust_greedy_deletion(
14306 map,
14307 selection.head(),
14308 cursor,
14309 action.ignore_brackets,
14310 );
14311 selection.set_head(cursor, SelectionGoal::None);
14312 }
14313 });
14314 });
14315 this.insert("", window, cx);
14316 });
14317 }
14318
14319 pub fn move_to_next_word_end(
14320 &mut self,
14321 _: &MoveToNextWordEnd,
14322 window: &mut Window,
14323 cx: &mut Context<Self>,
14324 ) {
14325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14326 self.change_selections(Default::default(), window, cx, |s| {
14327 s.move_cursors_with(|map, head, _| {
14328 (movement::next_word_end(map, head), SelectionGoal::None)
14329 });
14330 })
14331 }
14332
14333 pub fn move_to_next_subword_end(
14334 &mut self,
14335 _: &MoveToNextSubwordEnd,
14336 window: &mut Window,
14337 cx: &mut Context<Self>,
14338 ) {
14339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14340 self.change_selections(Default::default(), window, cx, |s| {
14341 s.move_cursors_with(|map, head, _| {
14342 (movement::next_subword_end(map, head), SelectionGoal::None)
14343 });
14344 })
14345 }
14346
14347 pub fn select_to_next_word_end(
14348 &mut self,
14349 _: &SelectToNextWordEnd,
14350 window: &mut Window,
14351 cx: &mut Context<Self>,
14352 ) {
14353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14354 self.change_selections(Default::default(), window, cx, |s| {
14355 s.move_heads_with(|map, head, _| {
14356 (movement::next_word_end(map, head), SelectionGoal::None)
14357 });
14358 })
14359 }
14360
14361 pub fn select_to_next_subword_end(
14362 &mut self,
14363 _: &SelectToNextSubwordEnd,
14364 window: &mut Window,
14365 cx: &mut Context<Self>,
14366 ) {
14367 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14368 self.change_selections(Default::default(), window, cx, |s| {
14369 s.move_heads_with(|map, head, _| {
14370 (movement::next_subword_end(map, head), SelectionGoal::None)
14371 });
14372 })
14373 }
14374
14375 pub fn delete_to_next_word_end(
14376 &mut self,
14377 action: &DeleteToNextWordEnd,
14378 window: &mut Window,
14379 cx: &mut Context<Self>,
14380 ) {
14381 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14382 self.transact(window, cx, |this, window, cx| {
14383 this.change_selections(Default::default(), window, cx, |s| {
14384 s.move_with(|map, selection| {
14385 if selection.is_empty() {
14386 let mut cursor = if action.ignore_newlines {
14387 movement::next_word_end(map, selection.head())
14388 } else {
14389 movement::next_word_end_or_newline(map, selection.head())
14390 };
14391 cursor = movement::adjust_greedy_deletion(
14392 map,
14393 selection.head(),
14394 cursor,
14395 action.ignore_brackets,
14396 );
14397 selection.set_head(cursor, SelectionGoal::None);
14398 }
14399 });
14400 });
14401 this.insert("", window, cx);
14402 });
14403 }
14404
14405 pub fn delete_to_next_subword_end(
14406 &mut self,
14407 action: &DeleteToNextSubwordEnd,
14408 window: &mut Window,
14409 cx: &mut Context<Self>,
14410 ) {
14411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14412 self.transact(window, cx, |this, window, cx| {
14413 this.change_selections(Default::default(), window, cx, |s| {
14414 s.move_with(|map, selection| {
14415 if selection.is_empty() {
14416 let mut cursor = if action.ignore_newlines {
14417 movement::next_subword_end(map, selection.head())
14418 } else {
14419 movement::next_subword_end_or_newline(map, selection.head())
14420 };
14421 cursor = movement::adjust_greedy_deletion(
14422 map,
14423 selection.head(),
14424 cursor,
14425 action.ignore_brackets,
14426 );
14427 selection.set_head(cursor, SelectionGoal::None);
14428 }
14429 });
14430 });
14431 this.insert("", window, cx);
14432 });
14433 }
14434
14435 pub fn move_to_beginning_of_line(
14436 &mut self,
14437 action: &MoveToBeginningOfLine,
14438 window: &mut Window,
14439 cx: &mut Context<Self>,
14440 ) {
14441 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14442 self.change_selections(Default::default(), window, cx, |s| {
14443 s.move_cursors_with(|map, head, _| {
14444 (
14445 movement::indented_line_beginning(
14446 map,
14447 head,
14448 action.stop_at_soft_wraps,
14449 action.stop_at_indent,
14450 ),
14451 SelectionGoal::None,
14452 )
14453 });
14454 })
14455 }
14456
14457 pub fn select_to_beginning_of_line(
14458 &mut self,
14459 action: &SelectToBeginningOfLine,
14460 window: &mut Window,
14461 cx: &mut Context<Self>,
14462 ) {
14463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14464 self.change_selections(Default::default(), window, cx, |s| {
14465 s.move_heads_with(|map, head, _| {
14466 (
14467 movement::indented_line_beginning(
14468 map,
14469 head,
14470 action.stop_at_soft_wraps,
14471 action.stop_at_indent,
14472 ),
14473 SelectionGoal::None,
14474 )
14475 });
14476 });
14477 }
14478
14479 pub fn delete_to_beginning_of_line(
14480 &mut self,
14481 action: &DeleteToBeginningOfLine,
14482 window: &mut Window,
14483 cx: &mut Context<Self>,
14484 ) {
14485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14486 self.transact(window, cx, |this, window, cx| {
14487 this.change_selections(Default::default(), window, cx, |s| {
14488 s.move_with(|_, selection| {
14489 selection.reversed = true;
14490 });
14491 });
14492
14493 this.select_to_beginning_of_line(
14494 &SelectToBeginningOfLine {
14495 stop_at_soft_wraps: false,
14496 stop_at_indent: action.stop_at_indent,
14497 },
14498 window,
14499 cx,
14500 );
14501 this.backspace(&Backspace, window, cx);
14502 });
14503 }
14504
14505 pub fn move_to_end_of_line(
14506 &mut self,
14507 action: &MoveToEndOfLine,
14508 window: &mut Window,
14509 cx: &mut Context<Self>,
14510 ) {
14511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14512 self.change_selections(Default::default(), window, cx, |s| {
14513 s.move_cursors_with(|map, head, _| {
14514 (
14515 movement::line_end(map, head, action.stop_at_soft_wraps),
14516 SelectionGoal::None,
14517 )
14518 });
14519 })
14520 }
14521
14522 pub fn select_to_end_of_line(
14523 &mut self,
14524 action: &SelectToEndOfLine,
14525 window: &mut Window,
14526 cx: &mut Context<Self>,
14527 ) {
14528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14529 self.change_selections(Default::default(), window, cx, |s| {
14530 s.move_heads_with(|map, head, _| {
14531 (
14532 movement::line_end(map, head, action.stop_at_soft_wraps),
14533 SelectionGoal::None,
14534 )
14535 });
14536 })
14537 }
14538
14539 pub fn delete_to_end_of_line(
14540 &mut self,
14541 _: &DeleteToEndOfLine,
14542 window: &mut Window,
14543 cx: &mut Context<Self>,
14544 ) {
14545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14546 self.transact(window, cx, |this, window, cx| {
14547 this.select_to_end_of_line(
14548 &SelectToEndOfLine {
14549 stop_at_soft_wraps: false,
14550 },
14551 window,
14552 cx,
14553 );
14554 this.delete(&Delete, window, cx);
14555 });
14556 }
14557
14558 pub fn cut_to_end_of_line(
14559 &mut self,
14560 action: &CutToEndOfLine,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
14564 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14565 self.transact(window, cx, |this, window, cx| {
14566 this.select_to_end_of_line(
14567 &SelectToEndOfLine {
14568 stop_at_soft_wraps: false,
14569 },
14570 window,
14571 cx,
14572 );
14573 if !action.stop_at_newlines {
14574 this.change_selections(Default::default(), window, cx, |s| {
14575 s.move_with(|_, sel| {
14576 if sel.is_empty() {
14577 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14578 }
14579 });
14580 });
14581 }
14582 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14583 let item = this.cut_common(false, window, cx);
14584 cx.write_to_clipboard(item);
14585 });
14586 }
14587
14588 pub fn move_to_start_of_paragraph(
14589 &mut self,
14590 _: &MoveToStartOfParagraph,
14591 window: &mut Window,
14592 cx: &mut Context<Self>,
14593 ) {
14594 if matches!(self.mode, EditorMode::SingleLine) {
14595 cx.propagate();
14596 return;
14597 }
14598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14599 self.change_selections(Default::default(), window, cx, |s| {
14600 s.move_with(|map, selection| {
14601 selection.collapse_to(
14602 movement::start_of_paragraph(map, selection.head(), 1),
14603 SelectionGoal::None,
14604 )
14605 });
14606 })
14607 }
14608
14609 pub fn move_to_end_of_paragraph(
14610 &mut self,
14611 _: &MoveToEndOfParagraph,
14612 window: &mut Window,
14613 cx: &mut Context<Self>,
14614 ) {
14615 if matches!(self.mode, EditorMode::SingleLine) {
14616 cx.propagate();
14617 return;
14618 }
14619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14620 self.change_selections(Default::default(), window, cx, |s| {
14621 s.move_with(|map, selection| {
14622 selection.collapse_to(
14623 movement::end_of_paragraph(map, selection.head(), 1),
14624 SelectionGoal::None,
14625 )
14626 });
14627 })
14628 }
14629
14630 pub fn select_to_start_of_paragraph(
14631 &mut self,
14632 _: &SelectToStartOfParagraph,
14633 window: &mut Window,
14634 cx: &mut Context<Self>,
14635 ) {
14636 if matches!(self.mode, EditorMode::SingleLine) {
14637 cx.propagate();
14638 return;
14639 }
14640 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14641 self.change_selections(Default::default(), window, cx, |s| {
14642 s.move_heads_with(|map, head, _| {
14643 (
14644 movement::start_of_paragraph(map, head, 1),
14645 SelectionGoal::None,
14646 )
14647 });
14648 })
14649 }
14650
14651 pub fn select_to_end_of_paragraph(
14652 &mut self,
14653 _: &SelectToEndOfParagraph,
14654 window: &mut Window,
14655 cx: &mut Context<Self>,
14656 ) {
14657 if matches!(self.mode, EditorMode::SingleLine) {
14658 cx.propagate();
14659 return;
14660 }
14661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14662 self.change_selections(Default::default(), window, cx, |s| {
14663 s.move_heads_with(|map, head, _| {
14664 (
14665 movement::end_of_paragraph(map, head, 1),
14666 SelectionGoal::None,
14667 )
14668 });
14669 })
14670 }
14671
14672 pub fn move_to_start_of_excerpt(
14673 &mut self,
14674 _: &MoveToStartOfExcerpt,
14675 window: &mut Window,
14676 cx: &mut Context<Self>,
14677 ) {
14678 if matches!(self.mode, EditorMode::SingleLine) {
14679 cx.propagate();
14680 return;
14681 }
14682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14683 self.change_selections(Default::default(), window, cx, |s| {
14684 s.move_with(|map, selection| {
14685 selection.collapse_to(
14686 movement::start_of_excerpt(
14687 map,
14688 selection.head(),
14689 workspace::searchable::Direction::Prev,
14690 ),
14691 SelectionGoal::None,
14692 )
14693 });
14694 })
14695 }
14696
14697 pub fn move_to_start_of_next_excerpt(
14698 &mut self,
14699 _: &MoveToStartOfNextExcerpt,
14700 window: &mut Window,
14701 cx: &mut Context<Self>,
14702 ) {
14703 if matches!(self.mode, EditorMode::SingleLine) {
14704 cx.propagate();
14705 return;
14706 }
14707
14708 self.change_selections(Default::default(), window, cx, |s| {
14709 s.move_with(|map, selection| {
14710 selection.collapse_to(
14711 movement::start_of_excerpt(
14712 map,
14713 selection.head(),
14714 workspace::searchable::Direction::Next,
14715 ),
14716 SelectionGoal::None,
14717 )
14718 });
14719 })
14720 }
14721
14722 pub fn move_to_end_of_excerpt(
14723 &mut self,
14724 _: &MoveToEndOfExcerpt,
14725 window: &mut Window,
14726 cx: &mut Context<Self>,
14727 ) {
14728 if matches!(self.mode, EditorMode::SingleLine) {
14729 cx.propagate();
14730 return;
14731 }
14732 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14733 self.change_selections(Default::default(), window, cx, |s| {
14734 s.move_with(|map, selection| {
14735 selection.collapse_to(
14736 movement::end_of_excerpt(
14737 map,
14738 selection.head(),
14739 workspace::searchable::Direction::Next,
14740 ),
14741 SelectionGoal::None,
14742 )
14743 });
14744 })
14745 }
14746
14747 pub fn move_to_end_of_previous_excerpt(
14748 &mut self,
14749 _: &MoveToEndOfPreviousExcerpt,
14750 window: &mut Window,
14751 cx: &mut Context<Self>,
14752 ) {
14753 if matches!(self.mode, EditorMode::SingleLine) {
14754 cx.propagate();
14755 return;
14756 }
14757 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14758 self.change_selections(Default::default(), window, cx, |s| {
14759 s.move_with(|map, selection| {
14760 selection.collapse_to(
14761 movement::end_of_excerpt(
14762 map,
14763 selection.head(),
14764 workspace::searchable::Direction::Prev,
14765 ),
14766 SelectionGoal::None,
14767 )
14768 });
14769 })
14770 }
14771
14772 pub fn select_to_start_of_excerpt(
14773 &mut self,
14774 _: &SelectToStartOfExcerpt,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) {
14778 if matches!(self.mode, EditorMode::SingleLine) {
14779 cx.propagate();
14780 return;
14781 }
14782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14783 self.change_selections(Default::default(), window, cx, |s| {
14784 s.move_heads_with(|map, head, _| {
14785 (
14786 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14787 SelectionGoal::None,
14788 )
14789 });
14790 })
14791 }
14792
14793 pub fn select_to_start_of_next_excerpt(
14794 &mut self,
14795 _: &SelectToStartOfNextExcerpt,
14796 window: &mut Window,
14797 cx: &mut Context<Self>,
14798 ) {
14799 if matches!(self.mode, EditorMode::SingleLine) {
14800 cx.propagate();
14801 return;
14802 }
14803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14804 self.change_selections(Default::default(), window, cx, |s| {
14805 s.move_heads_with(|map, head, _| {
14806 (
14807 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14808 SelectionGoal::None,
14809 )
14810 });
14811 })
14812 }
14813
14814 pub fn select_to_end_of_excerpt(
14815 &mut self,
14816 _: &SelectToEndOfExcerpt,
14817 window: &mut Window,
14818 cx: &mut Context<Self>,
14819 ) {
14820 if matches!(self.mode, EditorMode::SingleLine) {
14821 cx.propagate();
14822 return;
14823 }
14824 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14825 self.change_selections(Default::default(), window, cx, |s| {
14826 s.move_heads_with(|map, head, _| {
14827 (
14828 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14829 SelectionGoal::None,
14830 )
14831 });
14832 })
14833 }
14834
14835 pub fn select_to_end_of_previous_excerpt(
14836 &mut self,
14837 _: &SelectToEndOfPreviousExcerpt,
14838 window: &mut Window,
14839 cx: &mut Context<Self>,
14840 ) {
14841 if matches!(self.mode, EditorMode::SingleLine) {
14842 cx.propagate();
14843 return;
14844 }
14845 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14846 self.change_selections(Default::default(), window, cx, |s| {
14847 s.move_heads_with(|map, head, _| {
14848 (
14849 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14850 SelectionGoal::None,
14851 )
14852 });
14853 })
14854 }
14855
14856 pub fn move_to_beginning(
14857 &mut self,
14858 _: &MoveToBeginning,
14859 window: &mut Window,
14860 cx: &mut Context<Self>,
14861 ) {
14862 if matches!(self.mode, EditorMode::SingleLine) {
14863 cx.propagate();
14864 return;
14865 }
14866 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14867 self.change_selections(Default::default(), window, cx, |s| {
14868 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14869 });
14870 }
14871
14872 pub fn select_to_beginning(
14873 &mut self,
14874 _: &SelectToBeginning,
14875 window: &mut Window,
14876 cx: &mut Context<Self>,
14877 ) {
14878 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14879 selection.set_head(Point::zero(), SelectionGoal::None);
14880 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14881 self.change_selections(Default::default(), window, cx, |s| {
14882 s.select(vec![selection]);
14883 });
14884 }
14885
14886 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14887 if matches!(self.mode, EditorMode::SingleLine) {
14888 cx.propagate();
14889 return;
14890 }
14891 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14892 let cursor = self.buffer.read(cx).read(cx).len();
14893 self.change_selections(Default::default(), window, cx, |s| {
14894 s.select_ranges(vec![cursor..cursor])
14895 });
14896 }
14897
14898 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14899 self.nav_history = nav_history;
14900 }
14901
14902 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14903 self.nav_history.as_ref()
14904 }
14905
14906 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14907 self.push_to_nav_history(
14908 self.selections.newest_anchor().head(),
14909 None,
14910 false,
14911 true,
14912 cx,
14913 );
14914 }
14915
14916 fn navigation_data(&self, cursor_anchor: Anchor, cx: &App) -> NavigationData {
14917 let buffer = self.buffer.read(cx).read(cx);
14918 let cursor_position = cursor_anchor.to_point(&buffer);
14919 let scroll_anchor = self.scroll_manager.anchor();
14920 let scroll_top_row = scroll_anchor.top_row(&buffer);
14921 drop(buffer);
14922
14923 NavigationData {
14924 cursor_anchor,
14925 cursor_position,
14926 scroll_anchor,
14927 scroll_top_row,
14928 }
14929 }
14930
14931 fn navigation_entry(&self, cursor_anchor: Anchor, cx: &App) -> Option<NavigationEntry> {
14932 let Some(history) = self.nav_history.clone() else {
14933 return None;
14934 };
14935 let data = self.navigation_data(cursor_anchor, cx);
14936 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
14937 }
14938
14939 fn push_to_nav_history(
14940 &mut self,
14941 cursor_anchor: Anchor,
14942 new_position: Option<Point>,
14943 is_deactivate: bool,
14944 always: bool,
14945 cx: &mut Context<Self>,
14946 ) {
14947 let data = self.navigation_data(cursor_anchor, cx);
14948 if let Some(nav_history) = self.nav_history.as_mut() {
14949 if let Some(new_position) = new_position {
14950 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
14951 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14952 return;
14953 }
14954 }
14955
14956 nav_history.push(Some(data), cx);
14957 cx.emit(EditorEvent::PushedToNavHistory {
14958 anchor: cursor_anchor,
14959 is_deactivate,
14960 })
14961 }
14962 }
14963
14964 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14966 let buffer = self.buffer.read(cx).snapshot(cx);
14967 let mut selection = self
14968 .selections
14969 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14970 selection.set_head(buffer.len(), SelectionGoal::None);
14971 self.change_selections(Default::default(), window, cx, |s| {
14972 s.select(vec![selection]);
14973 });
14974 }
14975
14976 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14978 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14979 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14980 });
14981 }
14982
14983 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14986 let mut selections = self.selections.all::<Point>(&display_map);
14987 let max_point = display_map.buffer_snapshot().max_point();
14988 for selection in &mut selections {
14989 let rows = selection.spanned_rows(true, &display_map);
14990 selection.start = Point::new(rows.start.0, 0);
14991 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14992 selection.reversed = false;
14993 }
14994 self.change_selections(Default::default(), window, cx, |s| {
14995 s.select(selections);
14996 });
14997 }
14998
14999 pub fn split_selection_into_lines(
15000 &mut self,
15001 action: &SplitSelectionIntoLines,
15002 window: &mut Window,
15003 cx: &mut Context<Self>,
15004 ) {
15005 let selections = self
15006 .selections
15007 .all::<Point>(&self.display_snapshot(cx))
15008 .into_iter()
15009 .map(|selection| selection.start..selection.end)
15010 .collect::<Vec<_>>();
15011 self.unfold_ranges(&selections, true, true, cx);
15012
15013 let mut new_selection_ranges = Vec::new();
15014 {
15015 let buffer = self.buffer.read(cx).read(cx);
15016 for selection in selections {
15017 for row in selection.start.row..selection.end.row {
15018 let line_start = Point::new(row, 0);
15019 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15020
15021 if action.keep_selections {
15022 // Keep the selection range for each line
15023 let selection_start = if row == selection.start.row {
15024 selection.start
15025 } else {
15026 line_start
15027 };
15028 new_selection_ranges.push(selection_start..line_end);
15029 } else {
15030 // Collapse to cursor at end of line
15031 new_selection_ranges.push(line_end..line_end);
15032 }
15033 }
15034
15035 let is_multiline_selection = selection.start.row != selection.end.row;
15036 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15037 // so this action feels more ergonomic when paired with other selection operations
15038 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15039 if !should_skip_last {
15040 if action.keep_selections {
15041 if is_multiline_selection {
15042 let line_start = Point::new(selection.end.row, 0);
15043 new_selection_ranges.push(line_start..selection.end);
15044 } else {
15045 new_selection_ranges.push(selection.start..selection.end);
15046 }
15047 } else {
15048 new_selection_ranges.push(selection.end..selection.end);
15049 }
15050 }
15051 }
15052 }
15053 self.change_selections(Default::default(), window, cx, |s| {
15054 s.select_ranges(new_selection_ranges);
15055 });
15056 }
15057
15058 pub fn add_selection_above(
15059 &mut self,
15060 action: &AddSelectionAbove,
15061 window: &mut Window,
15062 cx: &mut Context<Self>,
15063 ) {
15064 self.add_selection(true, action.skip_soft_wrap, window, cx);
15065 }
15066
15067 pub fn add_selection_below(
15068 &mut self,
15069 action: &AddSelectionBelow,
15070 window: &mut Window,
15071 cx: &mut Context<Self>,
15072 ) {
15073 self.add_selection(false, action.skip_soft_wrap, window, cx);
15074 }
15075
15076 fn add_selection(
15077 &mut self,
15078 above: bool,
15079 skip_soft_wrap: bool,
15080 window: &mut Window,
15081 cx: &mut Context<Self>,
15082 ) {
15083 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15084
15085 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15086 let all_selections = self.selections.all::<Point>(&display_map);
15087 let text_layout_details = self.text_layout_details(window);
15088
15089 let (mut columnar_selections, new_selections_to_columnarize) = {
15090 if let Some(state) = self.add_selections_state.as_ref() {
15091 let columnar_selection_ids: HashSet<_> = state
15092 .groups
15093 .iter()
15094 .flat_map(|group| group.stack.iter())
15095 .copied()
15096 .collect();
15097
15098 all_selections
15099 .into_iter()
15100 .partition(|s| columnar_selection_ids.contains(&s.id))
15101 } else {
15102 (Vec::new(), all_selections)
15103 }
15104 };
15105
15106 let mut state = self
15107 .add_selections_state
15108 .take()
15109 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15110
15111 for selection in new_selections_to_columnarize {
15112 let range = selection.display_range(&display_map).sorted();
15113 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15114 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15115 let positions = start_x.min(end_x)..start_x.max(end_x);
15116 let mut stack = Vec::new();
15117 for row in range.start.row().0..=range.end.row().0 {
15118 if let Some(selection) = self.selections.build_columnar_selection(
15119 &display_map,
15120 DisplayRow(row),
15121 &positions,
15122 selection.reversed,
15123 &text_layout_details,
15124 ) {
15125 stack.push(selection.id);
15126 columnar_selections.push(selection);
15127 }
15128 }
15129 if !stack.is_empty() {
15130 if above {
15131 stack.reverse();
15132 }
15133 state.groups.push(AddSelectionsGroup { above, stack });
15134 }
15135 }
15136
15137 let mut final_selections = Vec::new();
15138 let end_row = if above {
15139 DisplayRow(0)
15140 } else {
15141 display_map.max_point().row()
15142 };
15143
15144 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15145 // positions to place new selections, so we need to keep track of the
15146 // column range of the oldest selection in each group, because
15147 // intermediate selections may have been clamped to shorter lines.
15148 // selections may have been clamped to shorter lines.
15149 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15150 let mut map = HashMap::default();
15151 for group in state.groups.iter() {
15152 if let Some(oldest_id) = group.stack.first() {
15153 if let Some(oldest_selection) =
15154 columnar_selections.iter().find(|s| s.id == *oldest_id)
15155 {
15156 let start_col = oldest_selection.start.column;
15157 let end_col = oldest_selection.end.column;
15158 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15159 for id in &group.stack {
15160 map.insert(*id, goal_columns.clone());
15161 }
15162 }
15163 }
15164 }
15165 map
15166 } else {
15167 HashMap::default()
15168 };
15169
15170 let mut last_added_item_per_group = HashMap::default();
15171 for group in state.groups.iter_mut() {
15172 if let Some(last_id) = group.stack.last() {
15173 last_added_item_per_group.insert(*last_id, group);
15174 }
15175 }
15176
15177 for selection in columnar_selections {
15178 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15179 if above == group.above {
15180 let range = selection.display_range(&display_map).sorted();
15181 debug_assert_eq!(range.start.row(), range.end.row());
15182 let row = range.start.row();
15183 let positions =
15184 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15185 Pixels::from(start)..Pixels::from(end)
15186 } else {
15187 let start_x =
15188 display_map.x_for_display_point(range.start, &text_layout_details);
15189 let end_x =
15190 display_map.x_for_display_point(range.end, &text_layout_details);
15191 start_x.min(end_x)..start_x.max(end_x)
15192 };
15193
15194 let maybe_new_selection = if skip_soft_wrap {
15195 let goal_columns = goal_columns_by_selection_id
15196 .remove(&selection.id)
15197 .unwrap_or_else(|| {
15198 let start_col = selection.start.column;
15199 let end_col = selection.end.column;
15200 start_col.min(end_col)..start_col.max(end_col)
15201 });
15202 self.selections.find_next_columnar_selection_by_buffer_row(
15203 &display_map,
15204 row,
15205 end_row,
15206 above,
15207 &goal_columns,
15208 selection.reversed,
15209 &text_layout_details,
15210 )
15211 } else {
15212 self.selections.find_next_columnar_selection_by_display_row(
15213 &display_map,
15214 row,
15215 end_row,
15216 above,
15217 &positions,
15218 selection.reversed,
15219 &text_layout_details,
15220 )
15221 };
15222
15223 if let Some(new_selection) = maybe_new_selection {
15224 group.stack.push(new_selection.id);
15225 if above {
15226 final_selections.push(new_selection);
15227 final_selections.push(selection);
15228 } else {
15229 final_selections.push(selection);
15230 final_selections.push(new_selection);
15231 }
15232 } else {
15233 final_selections.push(selection);
15234 }
15235 } else {
15236 group.stack.pop();
15237 }
15238 } else {
15239 final_selections.push(selection);
15240 }
15241 }
15242
15243 self.change_selections(Default::default(), window, cx, |s| {
15244 s.select(final_selections);
15245 });
15246
15247 let final_selection_ids: HashSet<_> = self
15248 .selections
15249 .all::<Point>(&display_map)
15250 .iter()
15251 .map(|s| s.id)
15252 .collect();
15253 state.groups.retain_mut(|group| {
15254 // selections might get merged above so we remove invalid items from stacks
15255 group.stack.retain(|id| final_selection_ids.contains(id));
15256
15257 // single selection in stack can be treated as initial state
15258 group.stack.len() > 1
15259 });
15260
15261 if !state.groups.is_empty() {
15262 self.add_selections_state = Some(state);
15263 }
15264 }
15265
15266 pub fn insert_snippet_at_selections(
15267 &mut self,
15268 action: &InsertSnippet,
15269 window: &mut Window,
15270 cx: &mut Context<Self>,
15271 ) {
15272 self.try_insert_snippet_at_selections(action, window, cx)
15273 .log_err();
15274 }
15275
15276 fn try_insert_snippet_at_selections(
15277 &mut self,
15278 action: &InsertSnippet,
15279 window: &mut Window,
15280 cx: &mut Context<Self>,
15281 ) -> Result<()> {
15282 let insertion_ranges = self
15283 .selections
15284 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15285 .into_iter()
15286 .map(|selection| selection.range())
15287 .collect_vec();
15288
15289 let snippet = if let Some(snippet_body) = &action.snippet {
15290 if action.language.is_none() && action.name.is_none() {
15291 Snippet::parse(snippet_body)?
15292 } else {
15293 bail!("`snippet` is mutually exclusive with `language` and `name`")
15294 }
15295 } else if let Some(name) = &action.name {
15296 let project = self.project().context("no project")?;
15297 let snippet_store = project.read(cx).snippets().read(cx);
15298 let snippet = snippet_store
15299 .snippets_for(action.language.clone(), cx)
15300 .into_iter()
15301 .find(|snippet| snippet.name == *name)
15302 .context("snippet not found")?;
15303 Snippet::parse(&snippet.body)?
15304 } else {
15305 // todo(andrew): open modal to select snippet
15306 bail!("`name` or `snippet` is required")
15307 };
15308
15309 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15310 }
15311
15312 fn select_match_ranges(
15313 &mut self,
15314 range: Range<MultiBufferOffset>,
15315 reversed: bool,
15316 replace_newest: bool,
15317 auto_scroll: Option<Autoscroll>,
15318 window: &mut Window,
15319 cx: &mut Context<Editor>,
15320 ) {
15321 self.unfold_ranges(
15322 std::slice::from_ref(&range),
15323 false,
15324 auto_scroll.is_some(),
15325 cx,
15326 );
15327 let effects = if let Some(scroll) = auto_scroll {
15328 SelectionEffects::scroll(scroll)
15329 } else {
15330 SelectionEffects::no_scroll()
15331 };
15332 self.change_selections(effects, window, cx, |s| {
15333 if replace_newest {
15334 s.delete(s.newest_anchor().id);
15335 }
15336 if reversed {
15337 s.insert_range(range.end..range.start);
15338 } else {
15339 s.insert_range(range);
15340 }
15341 });
15342 }
15343
15344 pub fn select_next_match_internal(
15345 &mut self,
15346 display_map: &DisplaySnapshot,
15347 replace_newest: bool,
15348 autoscroll: Option<Autoscroll>,
15349 window: &mut Window,
15350 cx: &mut Context<Self>,
15351 ) -> Result<()> {
15352 let buffer = display_map.buffer_snapshot();
15353 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15354 if let Some(mut select_next_state) = self.select_next_state.take() {
15355 let query = &select_next_state.query;
15356 if !select_next_state.done {
15357 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15358 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15359 let mut next_selected_range = None;
15360
15361 let bytes_after_last_selection =
15362 buffer.bytes_in_range(last_selection.end..buffer.len());
15363 let bytes_before_first_selection =
15364 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15365 let query_matches = query
15366 .stream_find_iter(bytes_after_last_selection)
15367 .map(|result| (last_selection.end, result))
15368 .chain(
15369 query
15370 .stream_find_iter(bytes_before_first_selection)
15371 .map(|result| (MultiBufferOffset(0), result)),
15372 );
15373
15374 for (start_offset, query_match) in query_matches {
15375 let query_match = query_match.unwrap(); // can only fail due to I/O
15376 let offset_range =
15377 start_offset + query_match.start()..start_offset + query_match.end();
15378
15379 if !select_next_state.wordwise
15380 || (!buffer.is_inside_word(offset_range.start, None)
15381 && !buffer.is_inside_word(offset_range.end, None))
15382 {
15383 let idx = selections
15384 .partition_point(|selection| selection.end <= offset_range.start);
15385 let overlaps = selections
15386 .get(idx)
15387 .map_or(false, |selection| selection.start < offset_range.end);
15388
15389 if !overlaps {
15390 next_selected_range = Some(offset_range);
15391 break;
15392 }
15393 }
15394 }
15395
15396 if let Some(next_selected_range) = next_selected_range {
15397 self.select_match_ranges(
15398 next_selected_range,
15399 last_selection.reversed,
15400 replace_newest,
15401 autoscroll,
15402 window,
15403 cx,
15404 );
15405 } else {
15406 select_next_state.done = true;
15407 }
15408 }
15409
15410 self.select_next_state = Some(select_next_state);
15411 } else {
15412 let mut only_carets = true;
15413 let mut same_text_selected = true;
15414 let mut selected_text = None;
15415
15416 let mut selections_iter = selections.iter().peekable();
15417 while let Some(selection) = selections_iter.next() {
15418 if selection.start != selection.end {
15419 only_carets = false;
15420 }
15421
15422 if same_text_selected {
15423 if selected_text.is_none() {
15424 selected_text =
15425 Some(buffer.text_for_range(selection.range()).collect::<String>());
15426 }
15427
15428 if let Some(next_selection) = selections_iter.peek() {
15429 if next_selection.len() == selection.len() {
15430 let next_selected_text = buffer
15431 .text_for_range(next_selection.range())
15432 .collect::<String>();
15433 if Some(next_selected_text) != selected_text {
15434 same_text_selected = false;
15435 selected_text = None;
15436 }
15437 } else {
15438 same_text_selected = false;
15439 selected_text = None;
15440 }
15441 }
15442 }
15443 }
15444
15445 if only_carets {
15446 for selection in &mut selections {
15447 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15448 selection.start = word_range.start;
15449 selection.end = word_range.end;
15450 selection.goal = SelectionGoal::None;
15451 selection.reversed = false;
15452 self.select_match_ranges(
15453 selection.start..selection.end,
15454 selection.reversed,
15455 replace_newest,
15456 autoscroll,
15457 window,
15458 cx,
15459 );
15460 }
15461
15462 if selections.len() == 1 {
15463 let selection = selections
15464 .last()
15465 .expect("ensured that there's only one selection");
15466 let query = buffer
15467 .text_for_range(selection.start..selection.end)
15468 .collect::<String>();
15469 let is_empty = query.is_empty();
15470 let select_state = SelectNextState {
15471 query: self.build_query(&[query], cx)?,
15472 wordwise: true,
15473 done: is_empty,
15474 };
15475 self.select_next_state = Some(select_state);
15476 } else {
15477 self.select_next_state = None;
15478 }
15479 } else if let Some(selected_text) = selected_text {
15480 self.select_next_state = Some(SelectNextState {
15481 query: self.build_query(&[selected_text], cx)?,
15482 wordwise: false,
15483 done: false,
15484 });
15485 self.select_next_match_internal(
15486 display_map,
15487 replace_newest,
15488 autoscroll,
15489 window,
15490 cx,
15491 )?;
15492 }
15493 }
15494 Ok(())
15495 }
15496
15497 pub fn select_all_matches(
15498 &mut self,
15499 _action: &SelectAllMatches,
15500 window: &mut Window,
15501 cx: &mut Context<Self>,
15502 ) -> Result<()> {
15503 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15504
15505 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15506
15507 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15508 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15509 else {
15510 return Ok(());
15511 };
15512
15513 let mut new_selections = Vec::new();
15514
15515 let reversed = self
15516 .selections
15517 .oldest::<MultiBufferOffset>(&display_map)
15518 .reversed;
15519 let buffer = display_map.buffer_snapshot();
15520 let query_matches = select_next_state
15521 .query
15522 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15523
15524 for query_match in query_matches.into_iter() {
15525 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15526 let offset_range = if reversed {
15527 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15528 } else {
15529 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15530 };
15531
15532 if !select_next_state.wordwise
15533 || (!buffer.is_inside_word(offset_range.start, None)
15534 && !buffer.is_inside_word(offset_range.end, None))
15535 {
15536 new_selections.push(offset_range.start..offset_range.end);
15537 }
15538 }
15539
15540 select_next_state.done = true;
15541
15542 if new_selections.is_empty() {
15543 log::error!("bug: new_selections is empty in select_all_matches");
15544 return Ok(());
15545 }
15546
15547 self.unfold_ranges(&new_selections, false, false, cx);
15548 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15549 selections.select_ranges(new_selections)
15550 });
15551
15552 Ok(())
15553 }
15554
15555 pub fn select_next(
15556 &mut self,
15557 action: &SelectNext,
15558 window: &mut Window,
15559 cx: &mut Context<Self>,
15560 ) -> Result<()> {
15561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15563 self.select_next_match_internal(
15564 &display_map,
15565 action.replace_newest,
15566 Some(Autoscroll::newest()),
15567 window,
15568 cx,
15569 )
15570 }
15571
15572 pub fn select_previous(
15573 &mut self,
15574 action: &SelectPrevious,
15575 window: &mut Window,
15576 cx: &mut Context<Self>,
15577 ) -> Result<()> {
15578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15580 let buffer = display_map.buffer_snapshot();
15581 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15582 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15583 let query = &select_prev_state.query;
15584 if !select_prev_state.done {
15585 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15586 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15587 let mut next_selected_range = None;
15588 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15589 let bytes_before_last_selection =
15590 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15591 let bytes_after_first_selection =
15592 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15593 let query_matches = query
15594 .stream_find_iter(bytes_before_last_selection)
15595 .map(|result| (last_selection.start, result))
15596 .chain(
15597 query
15598 .stream_find_iter(bytes_after_first_selection)
15599 .map(|result| (buffer.len(), result)),
15600 );
15601 for (end_offset, query_match) in query_matches {
15602 let query_match = query_match.unwrap(); // can only fail due to I/O
15603 let offset_range =
15604 end_offset - query_match.end()..end_offset - query_match.start();
15605
15606 if !select_prev_state.wordwise
15607 || (!buffer.is_inside_word(offset_range.start, None)
15608 && !buffer.is_inside_word(offset_range.end, None))
15609 {
15610 next_selected_range = Some(offset_range);
15611 break;
15612 }
15613 }
15614
15615 if let Some(next_selected_range) = next_selected_range {
15616 self.select_match_ranges(
15617 next_selected_range,
15618 last_selection.reversed,
15619 action.replace_newest,
15620 Some(Autoscroll::newest()),
15621 window,
15622 cx,
15623 );
15624 } else {
15625 select_prev_state.done = true;
15626 }
15627 }
15628
15629 self.select_prev_state = Some(select_prev_state);
15630 } else {
15631 let mut only_carets = true;
15632 let mut same_text_selected = true;
15633 let mut selected_text = None;
15634
15635 let mut selections_iter = selections.iter().peekable();
15636 while let Some(selection) = selections_iter.next() {
15637 if selection.start != selection.end {
15638 only_carets = false;
15639 }
15640
15641 if same_text_selected {
15642 if selected_text.is_none() {
15643 selected_text =
15644 Some(buffer.text_for_range(selection.range()).collect::<String>());
15645 }
15646
15647 if let Some(next_selection) = selections_iter.peek() {
15648 if next_selection.len() == selection.len() {
15649 let next_selected_text = buffer
15650 .text_for_range(next_selection.range())
15651 .collect::<String>();
15652 if Some(next_selected_text) != selected_text {
15653 same_text_selected = false;
15654 selected_text = None;
15655 }
15656 } else {
15657 same_text_selected = false;
15658 selected_text = None;
15659 }
15660 }
15661 }
15662 }
15663
15664 if only_carets {
15665 for selection in &mut selections {
15666 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15667 selection.start = word_range.start;
15668 selection.end = word_range.end;
15669 selection.goal = SelectionGoal::None;
15670 selection.reversed = false;
15671 self.select_match_ranges(
15672 selection.start..selection.end,
15673 selection.reversed,
15674 action.replace_newest,
15675 Some(Autoscroll::newest()),
15676 window,
15677 cx,
15678 );
15679 }
15680 if selections.len() == 1 {
15681 let selection = selections
15682 .last()
15683 .expect("ensured that there's only one selection");
15684 let query = buffer
15685 .text_for_range(selection.start..selection.end)
15686 .collect::<String>();
15687 let is_empty = query.is_empty();
15688 let select_state = SelectNextState {
15689 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15690 wordwise: true,
15691 done: is_empty,
15692 };
15693 self.select_prev_state = Some(select_state);
15694 } else {
15695 self.select_prev_state = None;
15696 }
15697 } else if let Some(selected_text) = selected_text {
15698 self.select_prev_state = Some(SelectNextState {
15699 query: self
15700 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15701 wordwise: false,
15702 done: false,
15703 });
15704 self.select_previous(action, window, cx)?;
15705 }
15706 }
15707 Ok(())
15708 }
15709
15710 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15711 /// setting the case sensitivity based on the global
15712 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15713 /// editor's settings.
15714 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15715 where
15716 I: IntoIterator<Item = P>,
15717 P: AsRef<[u8]>,
15718 {
15719 let case_sensitive = self
15720 .select_next_is_case_sensitive
15721 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15722
15723 let mut builder = AhoCorasickBuilder::new();
15724 builder.ascii_case_insensitive(!case_sensitive);
15725 builder.build(patterns)
15726 }
15727
15728 pub fn find_next_match(
15729 &mut self,
15730 _: &FindNextMatch,
15731 window: &mut Window,
15732 cx: &mut Context<Self>,
15733 ) -> Result<()> {
15734 let selections = self.selections.disjoint_anchors_arc();
15735 match selections.first() {
15736 Some(first) if selections.len() >= 2 => {
15737 self.change_selections(Default::default(), window, cx, |s| {
15738 s.select_ranges([first.range()]);
15739 });
15740 }
15741 _ => self.select_next(
15742 &SelectNext {
15743 replace_newest: true,
15744 },
15745 window,
15746 cx,
15747 )?,
15748 }
15749 Ok(())
15750 }
15751
15752 pub fn find_previous_match(
15753 &mut self,
15754 _: &FindPreviousMatch,
15755 window: &mut Window,
15756 cx: &mut Context<Self>,
15757 ) -> Result<()> {
15758 let selections = self.selections.disjoint_anchors_arc();
15759 match selections.last() {
15760 Some(last) if selections.len() >= 2 => {
15761 self.change_selections(Default::default(), window, cx, |s| {
15762 s.select_ranges([last.range()]);
15763 });
15764 }
15765 _ => self.select_previous(
15766 &SelectPrevious {
15767 replace_newest: true,
15768 },
15769 window,
15770 cx,
15771 )?,
15772 }
15773 Ok(())
15774 }
15775
15776 pub fn toggle_comments(
15777 &mut self,
15778 action: &ToggleComments,
15779 window: &mut Window,
15780 cx: &mut Context<Self>,
15781 ) {
15782 if self.read_only(cx) {
15783 return;
15784 }
15785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15786 let text_layout_details = &self.text_layout_details(window);
15787 self.transact(window, cx, |this, window, cx| {
15788 let mut selections = this
15789 .selections
15790 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15791 let mut edits = Vec::new();
15792 let mut selection_edit_ranges = Vec::new();
15793 let mut last_toggled_row = None;
15794 let snapshot = this.buffer.read(cx).read(cx);
15795 let empty_str: Arc<str> = Arc::default();
15796 let mut suffixes_inserted = Vec::new();
15797 let ignore_indent = action.ignore_indent;
15798
15799 fn comment_prefix_range(
15800 snapshot: &MultiBufferSnapshot,
15801 row: MultiBufferRow,
15802 comment_prefix: &str,
15803 comment_prefix_whitespace: &str,
15804 ignore_indent: bool,
15805 ) -> Range<Point> {
15806 let indent_size = if ignore_indent {
15807 0
15808 } else {
15809 snapshot.indent_size_for_line(row).len
15810 };
15811
15812 let start = Point::new(row.0, indent_size);
15813
15814 let mut line_bytes = snapshot
15815 .bytes_in_range(start..snapshot.max_point())
15816 .flatten()
15817 .copied();
15818
15819 // If this line currently begins with the line comment prefix, then record
15820 // the range containing the prefix.
15821 if line_bytes
15822 .by_ref()
15823 .take(comment_prefix.len())
15824 .eq(comment_prefix.bytes())
15825 {
15826 // Include any whitespace that matches the comment prefix.
15827 let matching_whitespace_len = line_bytes
15828 .zip(comment_prefix_whitespace.bytes())
15829 .take_while(|(a, b)| a == b)
15830 .count() as u32;
15831 let end = Point::new(
15832 start.row,
15833 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15834 );
15835 start..end
15836 } else {
15837 start..start
15838 }
15839 }
15840
15841 fn comment_suffix_range(
15842 snapshot: &MultiBufferSnapshot,
15843 row: MultiBufferRow,
15844 comment_suffix: &str,
15845 comment_suffix_has_leading_space: bool,
15846 ) -> Range<Point> {
15847 let end = Point::new(row.0, snapshot.line_len(row));
15848 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15849
15850 let mut line_end_bytes = snapshot
15851 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15852 .flatten()
15853 .copied();
15854
15855 let leading_space_len = if suffix_start_column > 0
15856 && line_end_bytes.next() == Some(b' ')
15857 && comment_suffix_has_leading_space
15858 {
15859 1
15860 } else {
15861 0
15862 };
15863
15864 // If this line currently begins with the line comment prefix, then record
15865 // the range containing the prefix.
15866 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15867 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15868 start..end
15869 } else {
15870 end..end
15871 }
15872 }
15873
15874 // TODO: Handle selections that cross excerpts
15875 for selection in &mut selections {
15876 let start_column = snapshot
15877 .indent_size_for_line(MultiBufferRow(selection.start.row))
15878 .len;
15879 let language = if let Some(language) =
15880 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15881 {
15882 language
15883 } else {
15884 continue;
15885 };
15886
15887 selection_edit_ranges.clear();
15888
15889 // If multiple selections contain a given row, avoid processing that
15890 // row more than once.
15891 let mut start_row = MultiBufferRow(selection.start.row);
15892 if last_toggled_row == Some(start_row) {
15893 start_row = start_row.next_row();
15894 }
15895 let end_row =
15896 if selection.end.row > selection.start.row && selection.end.column == 0 {
15897 MultiBufferRow(selection.end.row - 1)
15898 } else {
15899 MultiBufferRow(selection.end.row)
15900 };
15901 last_toggled_row = Some(end_row);
15902
15903 if start_row > end_row {
15904 continue;
15905 }
15906
15907 // If the language has line comments, toggle those.
15908 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15909
15910 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15911 if ignore_indent {
15912 full_comment_prefixes = full_comment_prefixes
15913 .into_iter()
15914 .map(|s| Arc::from(s.trim_end()))
15915 .collect();
15916 }
15917
15918 if !full_comment_prefixes.is_empty() {
15919 let first_prefix = full_comment_prefixes
15920 .first()
15921 .expect("prefixes is non-empty");
15922 let prefix_trimmed_lengths = full_comment_prefixes
15923 .iter()
15924 .map(|p| p.trim_end_matches(' ').len())
15925 .collect::<SmallVec<[usize; 4]>>();
15926
15927 let mut all_selection_lines_are_comments = true;
15928
15929 for row in start_row.0..=end_row.0 {
15930 let row = MultiBufferRow(row);
15931 if start_row < end_row && snapshot.is_line_blank(row) {
15932 continue;
15933 }
15934
15935 let prefix_range = full_comment_prefixes
15936 .iter()
15937 .zip(prefix_trimmed_lengths.iter().copied())
15938 .map(|(prefix, trimmed_prefix_len)| {
15939 comment_prefix_range(
15940 snapshot.deref(),
15941 row,
15942 &prefix[..trimmed_prefix_len],
15943 &prefix[trimmed_prefix_len..],
15944 ignore_indent,
15945 )
15946 })
15947 .max_by_key(|range| range.end.column - range.start.column)
15948 .expect("prefixes is non-empty");
15949
15950 if prefix_range.is_empty() {
15951 all_selection_lines_are_comments = false;
15952 }
15953
15954 selection_edit_ranges.push(prefix_range);
15955 }
15956
15957 if all_selection_lines_are_comments {
15958 edits.extend(
15959 selection_edit_ranges
15960 .iter()
15961 .cloned()
15962 .map(|range| (range, empty_str.clone())),
15963 );
15964 } else {
15965 let min_column = selection_edit_ranges
15966 .iter()
15967 .map(|range| range.start.column)
15968 .min()
15969 .unwrap_or(0);
15970 edits.extend(selection_edit_ranges.iter().map(|range| {
15971 let position = Point::new(range.start.row, min_column);
15972 (position..position, first_prefix.clone())
15973 }));
15974 }
15975 } else if let Some(BlockCommentConfig {
15976 start: full_comment_prefix,
15977 end: comment_suffix,
15978 ..
15979 }) = language.block_comment()
15980 {
15981 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15982 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15983 let prefix_range = comment_prefix_range(
15984 snapshot.deref(),
15985 start_row,
15986 comment_prefix,
15987 comment_prefix_whitespace,
15988 ignore_indent,
15989 );
15990 let suffix_range = comment_suffix_range(
15991 snapshot.deref(),
15992 end_row,
15993 comment_suffix.trim_start_matches(' '),
15994 comment_suffix.starts_with(' '),
15995 );
15996
15997 if prefix_range.is_empty() || suffix_range.is_empty() {
15998 edits.push((
15999 prefix_range.start..prefix_range.start,
16000 full_comment_prefix.clone(),
16001 ));
16002 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16003 suffixes_inserted.push((end_row, comment_suffix.len()));
16004 } else {
16005 edits.push((prefix_range, empty_str.clone()));
16006 edits.push((suffix_range, empty_str.clone()));
16007 }
16008 } else {
16009 continue;
16010 }
16011 }
16012
16013 drop(snapshot);
16014 this.buffer.update(cx, |buffer, cx| {
16015 buffer.edit(edits, None, cx);
16016 });
16017
16018 // Adjust selections so that they end before any comment suffixes that
16019 // were inserted.
16020 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16021 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16022 let snapshot = this.buffer.read(cx).read(cx);
16023 for selection in &mut selections {
16024 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16025 match row.cmp(&MultiBufferRow(selection.end.row)) {
16026 Ordering::Less => {
16027 suffixes_inserted.next();
16028 continue;
16029 }
16030 Ordering::Greater => break,
16031 Ordering::Equal => {
16032 if selection.end.column == snapshot.line_len(row) {
16033 if selection.is_empty() {
16034 selection.start.column -= suffix_len as u32;
16035 }
16036 selection.end.column -= suffix_len as u32;
16037 }
16038 break;
16039 }
16040 }
16041 }
16042 }
16043
16044 drop(snapshot);
16045 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16046
16047 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16048 let selections_on_single_row = selections.windows(2).all(|selections| {
16049 selections[0].start.row == selections[1].start.row
16050 && selections[0].end.row == selections[1].end.row
16051 && selections[0].start.row == selections[0].end.row
16052 });
16053 let selections_selecting = selections
16054 .iter()
16055 .any(|selection| selection.start != selection.end);
16056 let advance_downwards = action.advance_downwards
16057 && selections_on_single_row
16058 && !selections_selecting
16059 && !matches!(this.mode, EditorMode::SingleLine);
16060
16061 if advance_downwards {
16062 let snapshot = this.buffer.read(cx).snapshot(cx);
16063
16064 this.change_selections(Default::default(), window, cx, |s| {
16065 s.move_cursors_with(|display_snapshot, display_point, _| {
16066 let mut point = display_point.to_point(display_snapshot);
16067 point.row += 1;
16068 point = snapshot.clip_point(point, Bias::Left);
16069 let display_point = point.to_display_point(display_snapshot);
16070 let goal = SelectionGoal::HorizontalPosition(
16071 display_snapshot
16072 .x_for_display_point(display_point, text_layout_details)
16073 .into(),
16074 );
16075 (display_point, goal)
16076 })
16077 });
16078 }
16079 });
16080 }
16081
16082 pub fn select_enclosing_symbol(
16083 &mut self,
16084 _: &SelectEnclosingSymbol,
16085 window: &mut Window,
16086 cx: &mut Context<Self>,
16087 ) {
16088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16089
16090 let buffer = self.buffer.read(cx).snapshot(cx);
16091 let old_selections = self
16092 .selections
16093 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16094 .into_boxed_slice();
16095
16096 fn update_selection(
16097 selection: &Selection<MultiBufferOffset>,
16098 buffer_snap: &MultiBufferSnapshot,
16099 ) -> Option<Selection<MultiBufferOffset>> {
16100 let cursor = selection.head();
16101 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16102 for symbol in symbols.iter().rev() {
16103 let start = symbol.range.start.to_offset(buffer_snap);
16104 let end = symbol.range.end.to_offset(buffer_snap);
16105 let new_range = start..end;
16106 if start < selection.start || end > selection.end {
16107 return Some(Selection {
16108 id: selection.id,
16109 start: new_range.start,
16110 end: new_range.end,
16111 goal: SelectionGoal::None,
16112 reversed: selection.reversed,
16113 });
16114 }
16115 }
16116 None
16117 }
16118
16119 let mut selected_larger_symbol = false;
16120 let new_selections = old_selections
16121 .iter()
16122 .map(|selection| match update_selection(selection, &buffer) {
16123 Some(new_selection) => {
16124 if new_selection.range() != selection.range() {
16125 selected_larger_symbol = true;
16126 }
16127 new_selection
16128 }
16129 None => selection.clone(),
16130 })
16131 .collect::<Vec<_>>();
16132
16133 if selected_larger_symbol {
16134 self.change_selections(Default::default(), window, cx, |s| {
16135 s.select(new_selections);
16136 });
16137 }
16138 }
16139
16140 pub fn select_larger_syntax_node(
16141 &mut self,
16142 _: &SelectLargerSyntaxNode,
16143 window: &mut Window,
16144 cx: &mut Context<Self>,
16145 ) {
16146 let Some(visible_row_count) = self.visible_row_count() else {
16147 return;
16148 };
16149 let old_selections: Box<[_]> = self
16150 .selections
16151 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16152 .into();
16153 if old_selections.is_empty() {
16154 return;
16155 }
16156
16157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16158
16159 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16160 let buffer = self.buffer.read(cx).snapshot(cx);
16161
16162 let mut selected_larger_node = false;
16163 let mut new_selections = old_selections
16164 .iter()
16165 .map(|selection| {
16166 let old_range = selection.start..selection.end;
16167
16168 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16169 // manually select word at selection
16170 if ["string_content", "inline"].contains(&node.kind()) {
16171 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16172 // ignore if word is already selected
16173 if !word_range.is_empty() && old_range != word_range {
16174 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16175 // only select word if start and end point belongs to same word
16176 if word_range == last_word_range {
16177 selected_larger_node = true;
16178 return Selection {
16179 id: selection.id,
16180 start: word_range.start,
16181 end: word_range.end,
16182 goal: SelectionGoal::None,
16183 reversed: selection.reversed,
16184 };
16185 }
16186 }
16187 }
16188 }
16189
16190 let mut new_range = old_range.clone();
16191 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16192 new_range = range;
16193 if !node.is_named() {
16194 continue;
16195 }
16196 if !display_map.intersects_fold(new_range.start)
16197 && !display_map.intersects_fold(new_range.end)
16198 {
16199 break;
16200 }
16201 }
16202
16203 selected_larger_node |= new_range != old_range;
16204 Selection {
16205 id: selection.id,
16206 start: new_range.start,
16207 end: new_range.end,
16208 goal: SelectionGoal::None,
16209 reversed: selection.reversed,
16210 }
16211 })
16212 .collect::<Vec<_>>();
16213
16214 if !selected_larger_node {
16215 return; // don't put this call in the history
16216 }
16217
16218 // scroll based on transformation done to the last selection created by the user
16219 let (last_old, last_new) = old_selections
16220 .last()
16221 .zip(new_selections.last().cloned())
16222 .expect("old_selections isn't empty");
16223
16224 // revert selection
16225 let is_selection_reversed = {
16226 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16227 new_selections.last_mut().expect("checked above").reversed =
16228 should_newest_selection_be_reversed;
16229 should_newest_selection_be_reversed
16230 };
16231
16232 if selected_larger_node {
16233 self.select_syntax_node_history.disable_clearing = true;
16234 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16235 s.select(new_selections.clone());
16236 });
16237 self.select_syntax_node_history.disable_clearing = false;
16238 }
16239
16240 let start_row = last_new.start.to_display_point(&display_map).row().0;
16241 let end_row = last_new.end.to_display_point(&display_map).row().0;
16242 let selection_height = end_row - start_row + 1;
16243 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16244
16245 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16246 let scroll_behavior = if fits_on_the_screen {
16247 self.request_autoscroll(Autoscroll::fit(), cx);
16248 SelectSyntaxNodeScrollBehavior::FitSelection
16249 } else if is_selection_reversed {
16250 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16251 SelectSyntaxNodeScrollBehavior::CursorTop
16252 } else {
16253 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16254 SelectSyntaxNodeScrollBehavior::CursorBottom
16255 };
16256
16257 self.select_syntax_node_history.push((
16258 old_selections,
16259 scroll_behavior,
16260 is_selection_reversed,
16261 ));
16262 }
16263
16264 pub fn select_smaller_syntax_node(
16265 &mut self,
16266 _: &SelectSmallerSyntaxNode,
16267 window: &mut Window,
16268 cx: &mut Context<Self>,
16269 ) {
16270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16271
16272 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16273 self.select_syntax_node_history.pop()
16274 {
16275 if let Some(selection) = selections.last_mut() {
16276 selection.reversed = is_selection_reversed;
16277 }
16278
16279 self.select_syntax_node_history.disable_clearing = true;
16280 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16281 s.select(selections.to_vec());
16282 });
16283 self.select_syntax_node_history.disable_clearing = false;
16284
16285 match scroll_behavior {
16286 SelectSyntaxNodeScrollBehavior::CursorTop => {
16287 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16288 }
16289 SelectSyntaxNodeScrollBehavior::FitSelection => {
16290 self.request_autoscroll(Autoscroll::fit(), cx);
16291 }
16292 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16293 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16294 }
16295 }
16296 }
16297 }
16298
16299 pub fn unwrap_syntax_node(
16300 &mut self,
16301 _: &UnwrapSyntaxNode,
16302 window: &mut Window,
16303 cx: &mut Context<Self>,
16304 ) {
16305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16306
16307 let buffer = self.buffer.read(cx).snapshot(cx);
16308 let selections = self
16309 .selections
16310 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16311 .into_iter()
16312 // subtracting the offset requires sorting
16313 .sorted_by_key(|i| i.start);
16314
16315 let full_edits = selections
16316 .into_iter()
16317 .filter_map(|selection| {
16318 let child = if selection.is_empty()
16319 && let Some((_, ancestor_range)) =
16320 buffer.syntax_ancestor(selection.start..selection.end)
16321 {
16322 ancestor_range
16323 } else {
16324 selection.range()
16325 };
16326
16327 let mut parent = child.clone();
16328 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16329 parent = ancestor_range;
16330 if parent.start < child.start || parent.end > child.end {
16331 break;
16332 }
16333 }
16334
16335 if parent == child {
16336 return None;
16337 }
16338 let text = buffer.text_for_range(child).collect::<String>();
16339 Some((selection.id, parent, text))
16340 })
16341 .collect::<Vec<_>>();
16342 if full_edits.is_empty() {
16343 return;
16344 }
16345
16346 self.transact(window, cx, |this, window, cx| {
16347 this.buffer.update(cx, |buffer, cx| {
16348 buffer.edit(
16349 full_edits
16350 .iter()
16351 .map(|(_, p, t)| (p.clone(), t.clone()))
16352 .collect::<Vec<_>>(),
16353 None,
16354 cx,
16355 );
16356 });
16357 this.change_selections(Default::default(), window, cx, |s| {
16358 let mut offset = 0;
16359 let mut selections = vec![];
16360 for (id, parent, text) in full_edits {
16361 let start = parent.start - offset;
16362 offset += (parent.end - parent.start) - text.len();
16363 selections.push(Selection {
16364 id,
16365 start,
16366 end: start + text.len(),
16367 reversed: false,
16368 goal: Default::default(),
16369 });
16370 }
16371 s.select(selections);
16372 });
16373 });
16374 }
16375
16376 pub fn select_next_syntax_node(
16377 &mut self,
16378 _: &SelectNextSyntaxNode,
16379 window: &mut Window,
16380 cx: &mut Context<Self>,
16381 ) {
16382 let old_selections: Box<[_]> = self
16383 .selections
16384 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16385 .into();
16386 if old_selections.is_empty() {
16387 return;
16388 }
16389
16390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16391
16392 let buffer = self.buffer.read(cx).snapshot(cx);
16393 let mut selected_sibling = false;
16394
16395 let new_selections = old_selections
16396 .iter()
16397 .map(|selection| {
16398 let old_range = selection.start..selection.end;
16399
16400 let old_range =
16401 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16402 let excerpt = buffer.excerpt_containing(old_range.clone());
16403
16404 if let Some(mut excerpt) = excerpt
16405 && let Some(node) = excerpt
16406 .buffer()
16407 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16408 {
16409 let new_range = excerpt.map_range_from_buffer(
16410 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16411 );
16412 selected_sibling = true;
16413 Selection {
16414 id: selection.id,
16415 start: new_range.start,
16416 end: new_range.end,
16417 goal: SelectionGoal::None,
16418 reversed: selection.reversed,
16419 }
16420 } else {
16421 selection.clone()
16422 }
16423 })
16424 .collect::<Vec<_>>();
16425
16426 if selected_sibling {
16427 self.change_selections(
16428 SelectionEffects::scroll(Autoscroll::fit()),
16429 window,
16430 cx,
16431 |s| {
16432 s.select(new_selections);
16433 },
16434 );
16435 }
16436 }
16437
16438 pub fn select_prev_syntax_node(
16439 &mut self,
16440 _: &SelectPreviousSyntaxNode,
16441 window: &mut Window,
16442 cx: &mut Context<Self>,
16443 ) {
16444 let old_selections: Box<[_]> = self
16445 .selections
16446 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16447 .into();
16448 if old_selections.is_empty() {
16449 return;
16450 }
16451
16452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16453
16454 let buffer = self.buffer.read(cx).snapshot(cx);
16455 let mut selected_sibling = false;
16456
16457 let new_selections = old_selections
16458 .iter()
16459 .map(|selection| {
16460 let old_range = selection.start..selection.end;
16461 let old_range =
16462 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16463 let excerpt = buffer.excerpt_containing(old_range.clone());
16464
16465 if let Some(mut excerpt) = excerpt
16466 && let Some(node) = excerpt
16467 .buffer()
16468 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16469 {
16470 let new_range = excerpt.map_range_from_buffer(
16471 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16472 );
16473 selected_sibling = true;
16474 Selection {
16475 id: selection.id,
16476 start: new_range.start,
16477 end: new_range.end,
16478 goal: SelectionGoal::None,
16479 reversed: selection.reversed,
16480 }
16481 } else {
16482 selection.clone()
16483 }
16484 })
16485 .collect::<Vec<_>>();
16486
16487 if selected_sibling {
16488 self.change_selections(
16489 SelectionEffects::scroll(Autoscroll::fit()),
16490 window,
16491 cx,
16492 |s| {
16493 s.select(new_selections);
16494 },
16495 );
16496 }
16497 }
16498
16499 pub fn move_to_start_of_larger_syntax_node(
16500 &mut self,
16501 _: &MoveToStartOfLargerSyntaxNode,
16502 window: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) {
16505 self.move_cursors_to_syntax_nodes(window, cx, false);
16506 }
16507
16508 pub fn move_to_end_of_larger_syntax_node(
16509 &mut self,
16510 _: &MoveToEndOfLargerSyntaxNode,
16511 window: &mut Window,
16512 cx: &mut Context<Self>,
16513 ) {
16514 self.move_cursors_to_syntax_nodes(window, cx, true);
16515 }
16516
16517 fn move_cursors_to_syntax_nodes(
16518 &mut self,
16519 window: &mut Window,
16520 cx: &mut Context<Self>,
16521 move_to_end: bool,
16522 ) -> bool {
16523 let old_selections: Box<[_]> = self
16524 .selections
16525 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16526 .into();
16527 if old_selections.is_empty() {
16528 return false;
16529 }
16530
16531 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16532
16533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16534 let buffer = self.buffer.read(cx).snapshot(cx);
16535
16536 let mut any_cursor_moved = false;
16537 let new_selections = old_selections
16538 .iter()
16539 .map(|selection| {
16540 if !selection.is_empty() {
16541 return selection.clone();
16542 }
16543
16544 let selection_pos = selection.head();
16545 let old_range = selection_pos..selection_pos;
16546
16547 let mut new_pos = selection_pos;
16548 let mut search_range = old_range;
16549 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16550 search_range = range.clone();
16551 if !node.is_named()
16552 || display_map.intersects_fold(range.start)
16553 || display_map.intersects_fold(range.end)
16554 // If cursor is already at the end of the syntax node, continue searching
16555 || (move_to_end && range.end == selection_pos)
16556 // If cursor is already at the start of the syntax node, continue searching
16557 || (!move_to_end && range.start == selection_pos)
16558 {
16559 continue;
16560 }
16561
16562 // If we found a string_content node, find the largest parent that is still string_content
16563 // Enables us to skip to the end of strings without taking multiple steps inside the string
16564 let (_, final_range) = if node.kind() == "string_content" {
16565 let mut current_node = node;
16566 let mut current_range = range;
16567 while let Some((parent, parent_range)) =
16568 buffer.syntax_ancestor(current_range.clone())
16569 {
16570 if parent.kind() == "string_content" {
16571 current_node = parent;
16572 current_range = parent_range;
16573 } else {
16574 break;
16575 }
16576 }
16577
16578 (current_node, current_range)
16579 } else {
16580 (node, range)
16581 };
16582
16583 new_pos = if move_to_end {
16584 final_range.end
16585 } else {
16586 final_range.start
16587 };
16588
16589 break;
16590 }
16591
16592 any_cursor_moved |= new_pos != selection_pos;
16593
16594 Selection {
16595 id: selection.id,
16596 start: new_pos,
16597 end: new_pos,
16598 goal: SelectionGoal::None,
16599 reversed: false,
16600 }
16601 })
16602 .collect::<Vec<_>>();
16603
16604 self.change_selections(Default::default(), window, cx, |s| {
16605 s.select(new_selections);
16606 });
16607 self.request_autoscroll(Autoscroll::newest(), cx);
16608
16609 any_cursor_moved
16610 }
16611
16612 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16613 if !EditorSettings::get_global(cx).gutter.runnables {
16614 self.clear_tasks();
16615 return Task::ready(());
16616 }
16617 let project = self.project().map(Entity::downgrade);
16618 let task_sources = self.lsp_task_sources(cx);
16619 let multi_buffer = self.buffer.downgrade();
16620 cx.spawn_in(window, async move |editor, cx| {
16621 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16622 let Some(project) = project.and_then(|p| p.upgrade()) else {
16623 return;
16624 };
16625 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16626 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16627 }) else {
16628 return;
16629 };
16630
16631 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16632 if hide_runnables {
16633 return;
16634 }
16635 let new_rows =
16636 cx.background_spawn({
16637 let snapshot = display_snapshot.clone();
16638 async move {
16639 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16640 }
16641 })
16642 .await;
16643 let Ok(lsp_tasks) =
16644 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16645 else {
16646 return;
16647 };
16648 let lsp_tasks = lsp_tasks.await;
16649
16650 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16651 lsp_tasks
16652 .into_iter()
16653 .flat_map(|(kind, tasks)| {
16654 tasks.into_iter().filter_map(move |(location, task)| {
16655 Some((kind.clone(), location?, task))
16656 })
16657 })
16658 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16659 let buffer = location.target.buffer;
16660 let buffer_snapshot = buffer.read(cx).snapshot();
16661 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16662 |(excerpt_id, snapshot, _)| {
16663 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16664 display_snapshot
16665 .buffer_snapshot()
16666 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16667 } else {
16668 None
16669 }
16670 },
16671 );
16672 if let Some(offset) = offset {
16673 let task_buffer_range =
16674 location.target.range.to_point(&buffer_snapshot);
16675 let context_buffer_range =
16676 task_buffer_range.to_offset(&buffer_snapshot);
16677 let context_range = BufferOffset(context_buffer_range.start)
16678 ..BufferOffset(context_buffer_range.end);
16679
16680 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16681 .or_insert_with(|| RunnableTasks {
16682 templates: Vec::new(),
16683 offset,
16684 column: task_buffer_range.start.column,
16685 extra_variables: HashMap::default(),
16686 context_range,
16687 })
16688 .templates
16689 .push((kind, task.original_task().clone()));
16690 }
16691
16692 acc
16693 })
16694 }) else {
16695 return;
16696 };
16697
16698 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16699 buffer.language_settings(cx).tasks.prefer_lsp
16700 }) else {
16701 return;
16702 };
16703
16704 let rows = Self::runnable_rows(
16705 project,
16706 display_snapshot,
16707 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16708 new_rows,
16709 cx.clone(),
16710 )
16711 .await;
16712 editor
16713 .update(cx, |editor, _| {
16714 editor.clear_tasks();
16715 for (key, mut value) in rows {
16716 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16717 value.templates.extend(lsp_tasks.templates);
16718 }
16719
16720 editor.insert_tasks(key, value);
16721 }
16722 for (key, value) in lsp_tasks_by_rows {
16723 editor.insert_tasks(key, value);
16724 }
16725 })
16726 .ok();
16727 })
16728 }
16729 fn fetch_runnable_ranges(
16730 snapshot: &DisplaySnapshot,
16731 range: Range<Anchor>,
16732 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16733 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16734 }
16735
16736 fn runnable_rows(
16737 project: Entity<Project>,
16738 snapshot: DisplaySnapshot,
16739 prefer_lsp: bool,
16740 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16741 cx: AsyncWindowContext,
16742 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16743 cx.spawn(async move |cx| {
16744 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16745 for (run_range, mut runnable) in runnable_ranges {
16746 let Some(tasks) = cx
16747 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16748 .ok()
16749 else {
16750 continue;
16751 };
16752 let mut tasks = tasks.await;
16753
16754 if prefer_lsp {
16755 tasks.retain(|(task_kind, _)| {
16756 !matches!(task_kind, TaskSourceKind::Language { .. })
16757 });
16758 }
16759 if tasks.is_empty() {
16760 continue;
16761 }
16762
16763 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16764 let Some(row) = snapshot
16765 .buffer_snapshot()
16766 .buffer_line_for_row(MultiBufferRow(point.row))
16767 .map(|(_, range)| range.start.row)
16768 else {
16769 continue;
16770 };
16771
16772 let context_range =
16773 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16774 runnable_rows.push((
16775 (runnable.buffer_id, row),
16776 RunnableTasks {
16777 templates: tasks,
16778 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16779 context_range,
16780 column: point.column,
16781 extra_variables: runnable.extra_captures,
16782 },
16783 ));
16784 }
16785 runnable_rows
16786 })
16787 }
16788
16789 fn templates_with_tags(
16790 project: &Entity<Project>,
16791 runnable: &mut Runnable,
16792 cx: &mut App,
16793 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16794 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16795 let (worktree_id, file) = project
16796 .buffer_for_id(runnable.buffer, cx)
16797 .and_then(|buffer| buffer.read(cx).file())
16798 .map(|file| (file.worktree_id(cx), file.clone()))
16799 .unzip();
16800
16801 (
16802 project.task_store().read(cx).task_inventory().cloned(),
16803 worktree_id,
16804 file,
16805 )
16806 });
16807
16808 let tags = mem::take(&mut runnable.tags);
16809 let language = runnable.language.clone();
16810 cx.spawn(async move |cx| {
16811 let mut templates_with_tags = Vec::new();
16812 if let Some(inventory) = inventory {
16813 for RunnableTag(tag) in tags {
16814 let new_tasks = inventory.update(cx, |inventory, cx| {
16815 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16816 });
16817 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16818 move |(_, template)| {
16819 template.tags.iter().any(|source_tag| source_tag == &tag)
16820 },
16821 ));
16822 }
16823 }
16824 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16825
16826 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16827 // Strongest source wins; if we have worktree tag binding, prefer that to
16828 // global and language bindings;
16829 // if we have a global binding, prefer that to language binding.
16830 let first_mismatch = templates_with_tags
16831 .iter()
16832 .position(|(tag_source, _)| tag_source != leading_tag_source);
16833 if let Some(index) = first_mismatch {
16834 templates_with_tags.truncate(index);
16835 }
16836 }
16837
16838 templates_with_tags
16839 })
16840 }
16841
16842 pub fn move_to_enclosing_bracket(
16843 &mut self,
16844 _: &MoveToEnclosingBracket,
16845 window: &mut Window,
16846 cx: &mut Context<Self>,
16847 ) {
16848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16849 self.change_selections(Default::default(), window, cx, |s| {
16850 s.move_offsets_with(|snapshot, selection| {
16851 let Some(enclosing_bracket_ranges) =
16852 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16853 else {
16854 return;
16855 };
16856
16857 let mut best_length = usize::MAX;
16858 let mut best_inside = false;
16859 let mut best_in_bracket_range = false;
16860 let mut best_destination = None;
16861 for (open, close) in enclosing_bracket_ranges {
16862 let close = close.to_inclusive();
16863 let length = *close.end() - open.start;
16864 let inside = selection.start >= open.end && selection.end <= *close.start();
16865 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16866 || close.contains(&selection.head());
16867
16868 // If best is next to a bracket and current isn't, skip
16869 if !in_bracket_range && best_in_bracket_range {
16870 continue;
16871 }
16872
16873 // Prefer smaller lengths unless best is inside and current isn't
16874 if length > best_length && (best_inside || !inside) {
16875 continue;
16876 }
16877
16878 best_length = length;
16879 best_inside = inside;
16880 best_in_bracket_range = in_bracket_range;
16881 best_destination = Some(
16882 if close.contains(&selection.start) && close.contains(&selection.end) {
16883 if inside { open.end } else { open.start }
16884 } else if inside {
16885 *close.start()
16886 } else {
16887 *close.end()
16888 },
16889 );
16890 }
16891
16892 if let Some(destination) = best_destination {
16893 selection.collapse_to(destination, SelectionGoal::None);
16894 }
16895 })
16896 });
16897 }
16898
16899 pub fn undo_selection(
16900 &mut self,
16901 _: &UndoSelection,
16902 window: &mut Window,
16903 cx: &mut Context<Self>,
16904 ) {
16905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16906 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16907 self.selection_history.mode = SelectionHistoryMode::Undoing;
16908 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16909 this.end_selection(window, cx);
16910 this.change_selections(
16911 SelectionEffects::scroll(Autoscroll::newest()),
16912 window,
16913 cx,
16914 |s| s.select_anchors(entry.selections.to_vec()),
16915 );
16916 });
16917 self.selection_history.mode = SelectionHistoryMode::Normal;
16918
16919 self.select_next_state = entry.select_next_state;
16920 self.select_prev_state = entry.select_prev_state;
16921 self.add_selections_state = entry.add_selections_state;
16922 }
16923 }
16924
16925 pub fn redo_selection(
16926 &mut self,
16927 _: &RedoSelection,
16928 window: &mut Window,
16929 cx: &mut Context<Self>,
16930 ) {
16931 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16932 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16933 self.selection_history.mode = SelectionHistoryMode::Redoing;
16934 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16935 this.end_selection(window, cx);
16936 this.change_selections(
16937 SelectionEffects::scroll(Autoscroll::newest()),
16938 window,
16939 cx,
16940 |s| s.select_anchors(entry.selections.to_vec()),
16941 );
16942 });
16943 self.selection_history.mode = SelectionHistoryMode::Normal;
16944
16945 self.select_next_state = entry.select_next_state;
16946 self.select_prev_state = entry.select_prev_state;
16947 self.add_selections_state = entry.add_selections_state;
16948 }
16949 }
16950
16951 pub fn expand_excerpts(
16952 &mut self,
16953 action: &ExpandExcerpts,
16954 _: &mut Window,
16955 cx: &mut Context<Self>,
16956 ) {
16957 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16958 }
16959
16960 pub fn expand_excerpts_down(
16961 &mut self,
16962 action: &ExpandExcerptsDown,
16963 _: &mut Window,
16964 cx: &mut Context<Self>,
16965 ) {
16966 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16967 }
16968
16969 pub fn expand_excerpts_up(
16970 &mut self,
16971 action: &ExpandExcerptsUp,
16972 _: &mut Window,
16973 cx: &mut Context<Self>,
16974 ) {
16975 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16976 }
16977
16978 pub fn expand_excerpts_for_direction(
16979 &mut self,
16980 lines: u32,
16981 direction: ExpandExcerptDirection,
16982 cx: &mut Context<Self>,
16983 ) {
16984 let selections = self.selections.disjoint_anchors_arc();
16985
16986 let lines = if lines == 0 {
16987 EditorSettings::get_global(cx).expand_excerpt_lines
16988 } else {
16989 lines
16990 };
16991
16992 let snapshot = self.buffer.read(cx).snapshot(cx);
16993 let mut excerpt_ids = selections
16994 .iter()
16995 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16996 .collect::<Vec<_>>();
16997 excerpt_ids.sort();
16998 excerpt_ids.dedup();
16999
17000 if self.delegate_expand_excerpts {
17001 cx.emit(EditorEvent::ExpandExcerptsRequested {
17002 excerpt_ids,
17003 lines,
17004 direction,
17005 });
17006 return;
17007 }
17008
17009 self.buffer.update(cx, |buffer, cx| {
17010 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17011 })
17012 }
17013
17014 pub fn expand_excerpt(
17015 &mut self,
17016 excerpt: ExcerptId,
17017 direction: ExpandExcerptDirection,
17018 window: &mut Window,
17019 cx: &mut Context<Self>,
17020 ) {
17021 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17022
17023 if self.delegate_expand_excerpts {
17024 cx.emit(EditorEvent::ExpandExcerptsRequested {
17025 excerpt_ids: vec![excerpt],
17026 lines: lines_to_expand,
17027 direction,
17028 });
17029 return;
17030 }
17031
17032 let current_scroll_position = self.scroll_position(cx);
17033 let mut scroll = None;
17034
17035 if direction == ExpandExcerptDirection::Down {
17036 let multi_buffer = self.buffer.read(cx);
17037 let snapshot = multi_buffer.snapshot(cx);
17038 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17039 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17040 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17041 {
17042 let buffer_snapshot = buffer.read(cx).snapshot();
17043 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17044 let last_row = buffer_snapshot.max_point().row;
17045 let lines_below = last_row.saturating_sub(excerpt_end_row);
17046 if lines_below >= lines_to_expand {
17047 scroll = Some(
17048 current_scroll_position
17049 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17050 );
17051 }
17052 }
17053 }
17054 if direction == ExpandExcerptDirection::Up
17055 && self
17056 .buffer
17057 .read(cx)
17058 .snapshot(cx)
17059 .excerpt_before(excerpt)
17060 .is_none()
17061 {
17062 scroll = Some(current_scroll_position);
17063 }
17064
17065 self.buffer.update(cx, |buffer, cx| {
17066 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17067 });
17068
17069 if let Some(new_scroll_position) = scroll {
17070 self.set_scroll_position(new_scroll_position, window, cx);
17071 }
17072 }
17073
17074 pub fn go_to_singleton_buffer_point(
17075 &mut self,
17076 point: Point,
17077 window: &mut Window,
17078 cx: &mut Context<Self>,
17079 ) {
17080 self.go_to_singleton_buffer_range(point..point, window, cx);
17081 }
17082
17083 pub fn go_to_singleton_buffer_range(
17084 &mut self,
17085 range: Range<Point>,
17086 window: &mut Window,
17087 cx: &mut Context<Self>,
17088 ) {
17089 let multibuffer = self.buffer().read(cx);
17090 let Some(buffer) = multibuffer.as_singleton() else {
17091 return;
17092 };
17093 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17094 return;
17095 };
17096 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17097 return;
17098 };
17099 self.change_selections(
17100 SelectionEffects::default().nav_history(true),
17101 window,
17102 cx,
17103 |s| s.select_anchor_ranges([start..end]),
17104 );
17105 }
17106
17107 pub fn go_to_diagnostic(
17108 &mut self,
17109 action: &GoToDiagnostic,
17110 window: &mut Window,
17111 cx: &mut Context<Self>,
17112 ) {
17113 if !self.diagnostics_enabled() {
17114 return;
17115 }
17116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17117 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17118 }
17119
17120 pub fn go_to_prev_diagnostic(
17121 &mut self,
17122 action: &GoToPreviousDiagnostic,
17123 window: &mut Window,
17124 cx: &mut Context<Self>,
17125 ) {
17126 if !self.diagnostics_enabled() {
17127 return;
17128 }
17129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17130 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17131 }
17132
17133 pub fn go_to_diagnostic_impl(
17134 &mut self,
17135 direction: Direction,
17136 severity: GoToDiagnosticSeverityFilter,
17137 window: &mut Window,
17138 cx: &mut Context<Self>,
17139 ) {
17140 let buffer = self.buffer.read(cx).snapshot(cx);
17141 let selection = self
17142 .selections
17143 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17144
17145 let mut active_group_id = None;
17146 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17147 && active_group.active_range.start.to_offset(&buffer) == selection.start
17148 {
17149 active_group_id = Some(active_group.group_id);
17150 }
17151
17152 fn filtered<'a>(
17153 severity: GoToDiagnosticSeverityFilter,
17154 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17155 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17156 diagnostics
17157 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17158 .filter(|entry| entry.range.start != entry.range.end)
17159 .filter(|entry| !entry.diagnostic.is_unnecessary)
17160 }
17161
17162 let before = filtered(
17163 severity,
17164 buffer
17165 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17166 .filter(|entry| entry.range.start <= selection.start),
17167 );
17168 let after = filtered(
17169 severity,
17170 buffer
17171 .diagnostics_in_range(selection.start..buffer.len())
17172 .filter(|entry| entry.range.start >= selection.start),
17173 );
17174
17175 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17176 if direction == Direction::Prev {
17177 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17178 {
17179 for diagnostic in prev_diagnostics.into_iter().rev() {
17180 if diagnostic.range.start != selection.start
17181 || active_group_id
17182 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17183 {
17184 found = Some(diagnostic);
17185 break 'outer;
17186 }
17187 }
17188 }
17189 } else {
17190 for diagnostic in after.chain(before) {
17191 if diagnostic.range.start != selection.start
17192 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17193 {
17194 found = Some(diagnostic);
17195 break;
17196 }
17197 }
17198 }
17199 let Some(next_diagnostic) = found else {
17200 return;
17201 };
17202
17203 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17204 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17205 return;
17206 };
17207 let snapshot = self.snapshot(window, cx);
17208 if snapshot.intersects_fold(next_diagnostic.range.start) {
17209 self.unfold_ranges(
17210 std::slice::from_ref(&next_diagnostic.range),
17211 true,
17212 false,
17213 cx,
17214 );
17215 }
17216 self.change_selections(Default::default(), window, cx, |s| {
17217 s.select_ranges(vec![
17218 next_diagnostic.range.start..next_diagnostic.range.start,
17219 ])
17220 });
17221 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17222 self.refresh_edit_prediction(false, true, window, cx);
17223 }
17224
17225 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17227 let snapshot = self.snapshot(window, cx);
17228 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17229 self.go_to_hunk_before_or_after_position(
17230 &snapshot,
17231 selection.head(),
17232 Direction::Next,
17233 window,
17234 cx,
17235 );
17236 }
17237
17238 pub fn go_to_hunk_before_or_after_position(
17239 &mut self,
17240 snapshot: &EditorSnapshot,
17241 position: Point,
17242 direction: Direction,
17243 window: &mut Window,
17244 cx: &mut Context<Editor>,
17245 ) {
17246 let row = if direction == Direction::Next {
17247 self.hunk_after_position(snapshot, position)
17248 .map(|hunk| hunk.row_range.start)
17249 } else {
17250 self.hunk_before_position(snapshot, position)
17251 };
17252
17253 if let Some(row) = row {
17254 let destination = Point::new(row.0, 0);
17255 let autoscroll = Autoscroll::center();
17256
17257 self.unfold_ranges(&[destination..destination], false, false, cx);
17258 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17259 s.select_ranges([destination..destination]);
17260 });
17261 }
17262 }
17263
17264 fn hunk_after_position(
17265 &mut self,
17266 snapshot: &EditorSnapshot,
17267 position: Point,
17268 ) -> Option<MultiBufferDiffHunk> {
17269 snapshot
17270 .buffer_snapshot()
17271 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17272 .find(|hunk| hunk.row_range.start.0 > position.row)
17273 .or_else(|| {
17274 snapshot
17275 .buffer_snapshot()
17276 .diff_hunks_in_range(Point::zero()..position)
17277 .find(|hunk| hunk.row_range.end.0 < position.row)
17278 })
17279 }
17280
17281 fn go_to_prev_hunk(
17282 &mut self,
17283 _: &GoToPreviousHunk,
17284 window: &mut Window,
17285 cx: &mut Context<Self>,
17286 ) {
17287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17288 let snapshot = self.snapshot(window, cx);
17289 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17290 self.go_to_hunk_before_or_after_position(
17291 &snapshot,
17292 selection.head(),
17293 Direction::Prev,
17294 window,
17295 cx,
17296 );
17297 }
17298
17299 fn hunk_before_position(
17300 &mut self,
17301 snapshot: &EditorSnapshot,
17302 position: Point,
17303 ) -> Option<MultiBufferRow> {
17304 snapshot
17305 .buffer_snapshot()
17306 .diff_hunk_before(position)
17307 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17308 }
17309
17310 fn go_to_next_change(
17311 &mut self,
17312 _: &GoToNextChange,
17313 window: &mut Window,
17314 cx: &mut Context<Self>,
17315 ) {
17316 if let Some(selections) = self
17317 .change_list
17318 .next_change(1, Direction::Next)
17319 .map(|s| s.to_vec())
17320 {
17321 self.change_selections(Default::default(), window, cx, |s| {
17322 let map = s.display_snapshot();
17323 s.select_display_ranges(selections.iter().map(|a| {
17324 let point = a.to_display_point(&map);
17325 point..point
17326 }))
17327 })
17328 }
17329 }
17330
17331 fn go_to_previous_change(
17332 &mut self,
17333 _: &GoToPreviousChange,
17334 window: &mut Window,
17335 cx: &mut Context<Self>,
17336 ) {
17337 if let Some(selections) = self
17338 .change_list
17339 .next_change(1, Direction::Prev)
17340 .map(|s| s.to_vec())
17341 {
17342 self.change_selections(Default::default(), window, cx, |s| {
17343 let map = s.display_snapshot();
17344 s.select_display_ranges(selections.iter().map(|a| {
17345 let point = a.to_display_point(&map);
17346 point..point
17347 }))
17348 })
17349 }
17350 }
17351
17352 pub fn go_to_next_document_highlight(
17353 &mut self,
17354 _: &GoToNextDocumentHighlight,
17355 window: &mut Window,
17356 cx: &mut Context<Self>,
17357 ) {
17358 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17359 }
17360
17361 pub fn go_to_prev_document_highlight(
17362 &mut self,
17363 _: &GoToPreviousDocumentHighlight,
17364 window: &mut Window,
17365 cx: &mut Context<Self>,
17366 ) {
17367 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17368 }
17369
17370 pub fn go_to_document_highlight_before_or_after_position(
17371 &mut self,
17372 direction: Direction,
17373 window: &mut Window,
17374 cx: &mut Context<Editor>,
17375 ) {
17376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17377 let snapshot = self.snapshot(window, cx);
17378 let buffer = &snapshot.buffer_snapshot();
17379 let position = self
17380 .selections
17381 .newest::<Point>(&snapshot.display_snapshot)
17382 .head();
17383 let anchor_position = buffer.anchor_after(position);
17384
17385 // Get all document highlights (both read and write)
17386 let mut all_highlights = Vec::new();
17387
17388 if let Some((_, read_highlights)) = self
17389 .background_highlights
17390 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17391 {
17392 all_highlights.extend(read_highlights.iter());
17393 }
17394
17395 if let Some((_, write_highlights)) = self
17396 .background_highlights
17397 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17398 {
17399 all_highlights.extend(write_highlights.iter());
17400 }
17401
17402 if all_highlights.is_empty() {
17403 return;
17404 }
17405
17406 // Sort highlights by position
17407 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17408
17409 let target_highlight = match direction {
17410 Direction::Next => {
17411 // Find the first highlight after the current position
17412 all_highlights
17413 .iter()
17414 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17415 }
17416 Direction::Prev => {
17417 // Find the last highlight before the current position
17418 all_highlights
17419 .iter()
17420 .rev()
17421 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17422 }
17423 };
17424
17425 if let Some(highlight) = target_highlight {
17426 let destination = highlight.start.to_point(buffer);
17427 let autoscroll = Autoscroll::center();
17428
17429 self.unfold_ranges(&[destination..destination], false, false, cx);
17430 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17431 s.select_ranges([destination..destination]);
17432 });
17433 }
17434 }
17435
17436 fn go_to_line<T: 'static>(
17437 &mut self,
17438 position: Anchor,
17439 highlight_color: Option<Hsla>,
17440 window: &mut Window,
17441 cx: &mut Context<Self>,
17442 ) {
17443 let snapshot = self.snapshot(window, cx).display_snapshot;
17444 let position = position.to_point(&snapshot.buffer_snapshot());
17445 let start = snapshot
17446 .buffer_snapshot()
17447 .clip_point(Point::new(position.row, 0), Bias::Left);
17448 let end = start + Point::new(1, 0);
17449 let start = snapshot.buffer_snapshot().anchor_before(start);
17450 let end = snapshot.buffer_snapshot().anchor_before(end);
17451
17452 self.highlight_rows::<T>(
17453 start..end,
17454 highlight_color
17455 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17456 Default::default(),
17457 cx,
17458 );
17459
17460 if self.buffer.read(cx).is_singleton() {
17461 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17462 }
17463 }
17464
17465 pub fn go_to_definition(
17466 &mut self,
17467 _: &GoToDefinition,
17468 window: &mut Window,
17469 cx: &mut Context<Self>,
17470 ) -> Task<Result<Navigated>> {
17471 let definition =
17472 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17473 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17474 cx.spawn_in(window, async move |editor, cx| {
17475 if definition.await? == Navigated::Yes {
17476 return Ok(Navigated::Yes);
17477 }
17478 match fallback_strategy {
17479 GoToDefinitionFallback::None => Ok(Navigated::No),
17480 GoToDefinitionFallback::FindAllReferences => {
17481 match editor.update_in(cx, |editor, window, cx| {
17482 editor.find_all_references(&FindAllReferences::default(), window, cx)
17483 })? {
17484 Some(references) => references.await,
17485 None => Ok(Navigated::No),
17486 }
17487 }
17488 }
17489 })
17490 }
17491
17492 pub fn go_to_declaration(
17493 &mut self,
17494 _: &GoToDeclaration,
17495 window: &mut Window,
17496 cx: &mut Context<Self>,
17497 ) -> Task<Result<Navigated>> {
17498 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17499 }
17500
17501 pub fn go_to_declaration_split(
17502 &mut self,
17503 _: &GoToDeclaration,
17504 window: &mut Window,
17505 cx: &mut Context<Self>,
17506 ) -> Task<Result<Navigated>> {
17507 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17508 }
17509
17510 pub fn go_to_implementation(
17511 &mut self,
17512 _: &GoToImplementation,
17513 window: &mut Window,
17514 cx: &mut Context<Self>,
17515 ) -> Task<Result<Navigated>> {
17516 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17517 }
17518
17519 pub fn go_to_implementation_split(
17520 &mut self,
17521 _: &GoToImplementationSplit,
17522 window: &mut Window,
17523 cx: &mut Context<Self>,
17524 ) -> Task<Result<Navigated>> {
17525 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17526 }
17527
17528 pub fn go_to_type_definition(
17529 &mut self,
17530 _: &GoToTypeDefinition,
17531 window: &mut Window,
17532 cx: &mut Context<Self>,
17533 ) -> Task<Result<Navigated>> {
17534 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17535 }
17536
17537 pub fn go_to_definition_split(
17538 &mut self,
17539 _: &GoToDefinitionSplit,
17540 window: &mut Window,
17541 cx: &mut Context<Self>,
17542 ) -> Task<Result<Navigated>> {
17543 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17544 }
17545
17546 pub fn go_to_type_definition_split(
17547 &mut self,
17548 _: &GoToTypeDefinitionSplit,
17549 window: &mut Window,
17550 cx: &mut Context<Self>,
17551 ) -> Task<Result<Navigated>> {
17552 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17553 }
17554
17555 fn go_to_definition_of_kind(
17556 &mut self,
17557 kind: GotoDefinitionKind,
17558 split: bool,
17559 window: &mut Window,
17560 cx: &mut Context<Self>,
17561 ) -> Task<Result<Navigated>> {
17562 let Some(provider) = self.semantics_provider.clone() else {
17563 return Task::ready(Ok(Navigated::No));
17564 };
17565 let head = self
17566 .selections
17567 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17568 .head();
17569 let buffer = self.buffer.read(cx);
17570 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17571 return Task::ready(Ok(Navigated::No));
17572 };
17573 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17574 return Task::ready(Ok(Navigated::No));
17575 };
17576
17577 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17578
17579 cx.spawn_in(window, async move |editor, cx| {
17580 let Some(definitions) = definitions.await? else {
17581 return Ok(Navigated::No);
17582 };
17583 let navigated = editor
17584 .update_in(cx, |editor, window, cx| {
17585 editor.navigate_to_hover_links(
17586 Some(kind),
17587 definitions
17588 .into_iter()
17589 .filter(|location| {
17590 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17591 })
17592 .map(HoverLink::Text)
17593 .collect::<Vec<_>>(),
17594 nav_entry,
17595 split,
17596 window,
17597 cx,
17598 )
17599 })?
17600 .await?;
17601 anyhow::Ok(navigated)
17602 })
17603 }
17604
17605 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17606 let selection = self.selections.newest_anchor();
17607 let head = selection.head();
17608 let tail = selection.tail();
17609
17610 let Some((buffer, start_position)) =
17611 self.buffer.read(cx).text_anchor_for_position(head, cx)
17612 else {
17613 return;
17614 };
17615
17616 let end_position = if head != tail {
17617 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17618 return;
17619 };
17620 Some(pos)
17621 } else {
17622 None
17623 };
17624
17625 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17626 let url = if let Some(end_pos) = end_position {
17627 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17628 } else {
17629 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17630 };
17631
17632 if let Some(url) = url {
17633 cx.update(|window, cx| {
17634 if parse_zed_link(&url, cx).is_some() {
17635 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17636 } else {
17637 cx.open_url(&url);
17638 }
17639 })?;
17640 }
17641
17642 anyhow::Ok(())
17643 });
17644
17645 url_finder.detach();
17646 }
17647
17648 pub fn open_selected_filename(
17649 &mut self,
17650 _: &OpenSelectedFilename,
17651 window: &mut Window,
17652 cx: &mut Context<Self>,
17653 ) {
17654 let Some(workspace) = self.workspace() else {
17655 return;
17656 };
17657
17658 let position = self.selections.newest_anchor().head();
17659
17660 let Some((buffer, buffer_position)) =
17661 self.buffer.read(cx).text_anchor_for_position(position, cx)
17662 else {
17663 return;
17664 };
17665
17666 let project = self.project.clone();
17667
17668 cx.spawn_in(window, async move |_, cx| {
17669 let result = find_file(&buffer, project, buffer_position, cx).await;
17670
17671 if let Some((_, path)) = result {
17672 workspace
17673 .update_in(cx, |workspace, window, cx| {
17674 workspace.open_resolved_path(path, window, cx)
17675 })?
17676 .await?;
17677 }
17678 anyhow::Ok(())
17679 })
17680 .detach();
17681 }
17682
17683 pub(crate) fn navigate_to_hover_links(
17684 &mut self,
17685 kind: Option<GotoDefinitionKind>,
17686 definitions: Vec<HoverLink>,
17687 origin: Option<NavigationEntry>,
17688 split: bool,
17689 window: &mut Window,
17690 cx: &mut Context<Editor>,
17691 ) -> Task<Result<Navigated>> {
17692 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17693 let mut first_url_or_file = None;
17694 let definitions: Vec<_> = definitions
17695 .into_iter()
17696 .filter_map(|def| match def {
17697 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17698 HoverLink::InlayHint(lsp_location, server_id) => {
17699 let computation =
17700 self.compute_target_location(lsp_location, server_id, window, cx);
17701 Some(cx.background_spawn(computation))
17702 }
17703 HoverLink::Url(url) => {
17704 first_url_or_file = Some(Either::Left(url));
17705 None
17706 }
17707 HoverLink::File(path) => {
17708 first_url_or_file = Some(Either::Right(path));
17709 None
17710 }
17711 })
17712 .collect();
17713
17714 let workspace = self.workspace();
17715
17716 cx.spawn_in(window, async move |editor, cx| {
17717 let locations: Vec<Location> = future::join_all(definitions)
17718 .await
17719 .into_iter()
17720 .filter_map(|location| location.transpose())
17721 .collect::<Result<_>>()
17722 .context("location tasks")?;
17723 let mut locations = cx.update(|_, cx| {
17724 locations
17725 .into_iter()
17726 .map(|location| {
17727 let buffer = location.buffer.read(cx);
17728 (location.buffer, location.range.to_point(buffer))
17729 })
17730 .into_group_map()
17731 })?;
17732 let mut num_locations = 0;
17733 for ranges in locations.values_mut() {
17734 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17735 ranges.dedup();
17736 num_locations += ranges.len();
17737 }
17738
17739 if num_locations > 1 {
17740 let tab_kind = match kind {
17741 Some(GotoDefinitionKind::Implementation) => "Implementations",
17742 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17743 Some(GotoDefinitionKind::Declaration) => "Declarations",
17744 Some(GotoDefinitionKind::Type) => "Types",
17745 };
17746 let title = editor
17747 .update_in(cx, |_, _, cx| {
17748 let target = locations
17749 .iter()
17750 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17751 .map(|(buffer, location)| {
17752 buffer
17753 .read(cx)
17754 .text_for_range(location.clone())
17755 .collect::<String>()
17756 })
17757 .filter(|text| !text.contains('\n'))
17758 .unique()
17759 .take(3)
17760 .join(", ");
17761 if target.is_empty() {
17762 tab_kind.to_owned()
17763 } else {
17764 format!("{tab_kind} for {target}")
17765 }
17766 })
17767 .context("buffer title")?;
17768
17769 let Some(workspace) = workspace else {
17770 return Ok(Navigated::No);
17771 };
17772
17773 let opened = workspace
17774 .update_in(cx, |workspace, window, cx| {
17775 let allow_preview = PreviewTabsSettings::get_global(cx)
17776 .enable_preview_multibuffer_from_code_navigation;
17777 if let Some((target_editor, target_pane)) =
17778 Self::open_locations_in_multibuffer(
17779 workspace,
17780 locations,
17781 title,
17782 split,
17783 allow_preview,
17784 MultibufferSelectionMode::First,
17785 window,
17786 cx,
17787 )
17788 {
17789 // We create our own nav history instead of using
17790 // `target_editor.nav_history` because `nav_history`
17791 // seems to be populated asynchronously when an item
17792 // is added to a pane
17793 let mut nav_history = target_pane
17794 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17795 target_editor.update(cx, |editor, cx| {
17796 let nav_data = editor
17797 .navigation_data(editor.selections.newest_anchor().head(), cx);
17798 let target =
17799 Some(nav_history.navigation_entry(Some(
17800 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17801 )));
17802 nav_history.push_tag(origin, target);
17803 })
17804 }
17805 })
17806 .is_ok();
17807
17808 anyhow::Ok(Navigated::from_bool(opened))
17809 } else if num_locations == 0 {
17810 // If there is one url or file, open it directly
17811 match first_url_or_file {
17812 Some(Either::Left(url)) => {
17813 cx.update(|window, cx| {
17814 if parse_zed_link(&url, cx).is_some() {
17815 window
17816 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17817 } else {
17818 cx.open_url(&url);
17819 }
17820 })?;
17821 Ok(Navigated::Yes)
17822 }
17823 Some(Either::Right(path)) => {
17824 // TODO(andrew): respect preview tab settings
17825 // `enable_keep_preview_on_code_navigation` and
17826 // `enable_preview_file_from_code_navigation`
17827 let Some(workspace) = workspace else {
17828 return Ok(Navigated::No);
17829 };
17830 workspace
17831 .update_in(cx, |workspace, window, cx| {
17832 workspace.open_resolved_path(path, window, cx)
17833 })?
17834 .await?;
17835 Ok(Navigated::Yes)
17836 }
17837 None => Ok(Navigated::No),
17838 }
17839 } else {
17840 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17841 let target_range = target_ranges.first().unwrap().clone();
17842
17843 editor.update_in(cx, |editor, window, cx| {
17844 let range = editor.range_for_match(&target_range);
17845 let range = collapse_multiline_range(range);
17846
17847 if !split
17848 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17849 {
17850 editor.go_to_singleton_buffer_range(range, window, cx);
17851
17852 let target =
17853 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
17854 if let Some(mut nav_history) = editor.nav_history.clone() {
17855 nav_history.push_tag(origin, target);
17856 }
17857 } else {
17858 let Some(workspace) = workspace else {
17859 return Navigated::No;
17860 };
17861 let pane = workspace.read(cx).active_pane().clone();
17862 window.defer(cx, move |window, cx| {
17863 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
17864 workspace.update(cx, |workspace, cx| {
17865 let pane = if split {
17866 workspace.adjacent_pane(window, cx)
17867 } else {
17868 workspace.active_pane().clone()
17869 };
17870
17871 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17872 let keep_old_preview = preview_tabs_settings
17873 .enable_keep_preview_on_code_navigation;
17874 let allow_new_preview = preview_tabs_settings
17875 .enable_preview_file_from_code_navigation;
17876
17877 let editor = workspace.open_project_item(
17878 pane.clone(),
17879 target_buffer.clone(),
17880 true,
17881 true,
17882 keep_old_preview,
17883 allow_new_preview,
17884 window,
17885 cx,
17886 );
17887 (editor, pane)
17888 });
17889 // We create our own nav history instead of using
17890 // `target_editor.nav_history` because `nav_history`
17891 // seems to be populated asynchronously when an item
17892 // is added to a pane
17893 let mut nav_history = target_pane
17894 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17895 target_editor.update(cx, |target_editor, cx| {
17896 // When selecting a definition in a different buffer, disable the nav history
17897 // to avoid creating a history entry at the previous cursor location.
17898 pane.update(cx, |pane, _| pane.disable_history());
17899 target_editor.go_to_singleton_buffer_range(range, window, cx);
17900
17901 let nav_data = target_editor.navigation_data(
17902 target_editor.selections.newest_anchor().head(),
17903 cx,
17904 );
17905 let target =
17906 Some(nav_history.navigation_entry(Some(
17907 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17908 )));
17909 nav_history.push_tag(origin, target);
17910 pane.update(cx, |pane, _| pane.enable_history());
17911 });
17912 });
17913 }
17914 Navigated::Yes
17915 })
17916 }
17917 })
17918 }
17919
17920 fn compute_target_location(
17921 &self,
17922 lsp_location: lsp::Location,
17923 server_id: LanguageServerId,
17924 window: &mut Window,
17925 cx: &mut Context<Self>,
17926 ) -> Task<anyhow::Result<Option<Location>>> {
17927 let Some(project) = self.project.clone() else {
17928 return Task::ready(Ok(None));
17929 };
17930
17931 cx.spawn_in(window, async move |editor, cx| {
17932 let location_task = editor.update(cx, |_, cx| {
17933 project.update(cx, |project, cx| {
17934 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17935 })
17936 })?;
17937 let location = Some({
17938 let target_buffer_handle = location_task.await.context("open local buffer")?;
17939 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17940 let target_start = target_buffer
17941 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17942 let target_end = target_buffer
17943 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17944 target_buffer.anchor_after(target_start)
17945 ..target_buffer.anchor_before(target_end)
17946 });
17947 Location {
17948 buffer: target_buffer_handle,
17949 range,
17950 }
17951 });
17952 Ok(location)
17953 })
17954 }
17955
17956 fn go_to_next_reference(
17957 &mut self,
17958 _: &GoToNextReference,
17959 window: &mut Window,
17960 cx: &mut Context<Self>,
17961 ) {
17962 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17963 if let Some(task) = task {
17964 task.detach();
17965 };
17966 }
17967
17968 fn go_to_prev_reference(
17969 &mut self,
17970 _: &GoToPreviousReference,
17971 window: &mut Window,
17972 cx: &mut Context<Self>,
17973 ) {
17974 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17975 if let Some(task) = task {
17976 task.detach();
17977 };
17978 }
17979
17980 pub fn go_to_reference_before_or_after_position(
17981 &mut self,
17982 direction: Direction,
17983 count: usize,
17984 window: &mut Window,
17985 cx: &mut Context<Self>,
17986 ) -> Option<Task<Result<()>>> {
17987 let selection = self.selections.newest_anchor();
17988 let head = selection.head();
17989
17990 let multi_buffer = self.buffer.read(cx);
17991
17992 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17993 let workspace = self.workspace()?;
17994 let project = workspace.read(cx).project().clone();
17995 let references =
17996 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17997 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17998 let Some(locations) = references.await? else {
17999 return Ok(());
18000 };
18001
18002 if locations.is_empty() {
18003 // totally normal - the cursor may be on something which is not
18004 // a symbol (e.g. a keyword)
18005 log::info!("no references found under cursor");
18006 return Ok(());
18007 }
18008
18009 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18010
18011 let (locations, current_location_index) =
18012 multi_buffer.update(cx, |multi_buffer, cx| {
18013 let mut locations = locations
18014 .into_iter()
18015 .filter_map(|loc| {
18016 let start = multi_buffer.buffer_anchor_to_anchor(
18017 &loc.buffer,
18018 loc.range.start,
18019 cx,
18020 )?;
18021 let end = multi_buffer.buffer_anchor_to_anchor(
18022 &loc.buffer,
18023 loc.range.end,
18024 cx,
18025 )?;
18026 Some(start..end)
18027 })
18028 .collect::<Vec<_>>();
18029
18030 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18031 // There is an O(n) implementation, but given this list will be
18032 // small (usually <100 items), the extra O(log(n)) factor isn't
18033 // worth the (surprisingly large amount of) extra complexity.
18034 locations
18035 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18036
18037 let head_offset = head.to_offset(&multi_buffer_snapshot);
18038
18039 let current_location_index = locations.iter().position(|loc| {
18040 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18041 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18042 });
18043
18044 (locations, current_location_index)
18045 });
18046
18047 let Some(current_location_index) = current_location_index else {
18048 // This indicates something has gone wrong, because we already
18049 // handle the "no references" case above
18050 log::error!(
18051 "failed to find current reference under cursor. Total references: {}",
18052 locations.len()
18053 );
18054 return Ok(());
18055 };
18056
18057 let destination_location_index = match direction {
18058 Direction::Next => (current_location_index + count) % locations.len(),
18059 Direction::Prev => {
18060 (current_location_index + locations.len() - count % locations.len())
18061 % locations.len()
18062 }
18063 };
18064
18065 // TODO(cameron): is this needed?
18066 // the thinking is to avoid "jumping to the current location" (avoid
18067 // polluting "jumplist" in vim terms)
18068 if current_location_index == destination_location_index {
18069 return Ok(());
18070 }
18071
18072 let Range { start, end } = locations[destination_location_index];
18073
18074 editor.update_in(cx, |editor, window, cx| {
18075 let effects = SelectionEffects::default();
18076
18077 editor.unfold_ranges(&[start..end], false, false, cx);
18078 editor.change_selections(effects, window, cx, |s| {
18079 s.select_ranges([start..start]);
18080 });
18081 })?;
18082
18083 Ok(())
18084 }))
18085 }
18086
18087 pub fn find_all_references(
18088 &mut self,
18089 action: &FindAllReferences,
18090 window: &mut Window,
18091 cx: &mut Context<Self>,
18092 ) -> Option<Task<Result<Navigated>>> {
18093 let always_open_multibuffer = action.always_open_multibuffer;
18094 let selection = self.selections.newest_anchor();
18095 let multi_buffer = self.buffer.read(cx);
18096 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18097 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18098 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18099 let head = selection_offset.head();
18100
18101 let head_anchor = multi_buffer_snapshot.anchor_at(
18102 head,
18103 if head < selection_offset.tail() {
18104 Bias::Right
18105 } else {
18106 Bias::Left
18107 },
18108 );
18109
18110 match self
18111 .find_all_references_task_sources
18112 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18113 {
18114 Ok(_) => {
18115 log::info!(
18116 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18117 );
18118 return None;
18119 }
18120 Err(i) => {
18121 self.find_all_references_task_sources.insert(i, head_anchor);
18122 }
18123 }
18124
18125 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18126 let workspace = self.workspace()?;
18127 let project = workspace.read(cx).project().clone();
18128 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18129 Some(cx.spawn_in(window, async move |editor, cx| {
18130 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18131 if let Ok(i) = editor
18132 .find_all_references_task_sources
18133 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18134 {
18135 editor.find_all_references_task_sources.remove(i);
18136 }
18137 });
18138
18139 let Some(locations) = references.await? else {
18140 return anyhow::Ok(Navigated::No);
18141 };
18142 let mut locations = cx.update(|_, cx| {
18143 locations
18144 .into_iter()
18145 .map(|location| {
18146 let buffer = location.buffer.read(cx);
18147 (location.buffer, location.range.to_point(buffer))
18148 })
18149 // if special-casing the single-match case, remove ranges
18150 // that intersect current selection
18151 .filter(|(location_buffer, location)| {
18152 if always_open_multibuffer || &buffer != location_buffer {
18153 return true;
18154 }
18155
18156 !location.contains_inclusive(&selection_point.range())
18157 })
18158 .into_group_map()
18159 })?;
18160 if locations.is_empty() {
18161 return anyhow::Ok(Navigated::No);
18162 }
18163 for ranges in locations.values_mut() {
18164 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18165 ranges.dedup();
18166 }
18167 let mut num_locations = 0;
18168 for ranges in locations.values_mut() {
18169 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18170 ranges.dedup();
18171 num_locations += ranges.len();
18172 }
18173
18174 if num_locations == 1 && !always_open_multibuffer {
18175 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18176 let target_range = target_ranges.first().unwrap().clone();
18177
18178 return editor.update_in(cx, |editor, window, cx| {
18179 let range = target_range.to_point(target_buffer.read(cx));
18180 let range = editor.range_for_match(&range);
18181 let range = range.start..range.start;
18182
18183 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18184 editor.go_to_singleton_buffer_range(range, window, cx);
18185 } else {
18186 let pane = workspace.read(cx).active_pane().clone();
18187 window.defer(cx, move |window, cx| {
18188 let target_editor: Entity<Self> =
18189 workspace.update(cx, |workspace, cx| {
18190 let pane = workspace.active_pane().clone();
18191
18192 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18193 let keep_old_preview = preview_tabs_settings
18194 .enable_keep_preview_on_code_navigation;
18195 let allow_new_preview = preview_tabs_settings
18196 .enable_preview_file_from_code_navigation;
18197
18198 workspace.open_project_item(
18199 pane,
18200 target_buffer.clone(),
18201 true,
18202 true,
18203 keep_old_preview,
18204 allow_new_preview,
18205 window,
18206 cx,
18207 )
18208 });
18209 target_editor.update(cx, |target_editor, cx| {
18210 // When selecting a definition in a different buffer, disable the nav history
18211 // to avoid creating a history entry at the previous cursor location.
18212 pane.update(cx, |pane, _| pane.disable_history());
18213 target_editor.go_to_singleton_buffer_range(range, window, cx);
18214 pane.update(cx, |pane, _| pane.enable_history());
18215 });
18216 });
18217 }
18218 Navigated::No
18219 });
18220 }
18221
18222 workspace.update_in(cx, |workspace, window, cx| {
18223 let target = locations
18224 .iter()
18225 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18226 .map(|(buffer, location)| {
18227 buffer
18228 .read(cx)
18229 .text_for_range(location.clone())
18230 .collect::<String>()
18231 })
18232 .filter(|text| !text.contains('\n'))
18233 .unique()
18234 .take(3)
18235 .join(", ");
18236 let title = if target.is_empty() {
18237 "References".to_owned()
18238 } else {
18239 format!("References to {target}")
18240 };
18241 let allow_preview = PreviewTabsSettings::get_global(cx)
18242 .enable_preview_multibuffer_from_code_navigation;
18243 Self::open_locations_in_multibuffer(
18244 workspace,
18245 locations,
18246 title,
18247 false,
18248 allow_preview,
18249 MultibufferSelectionMode::First,
18250 window,
18251 cx,
18252 );
18253 Navigated::Yes
18254 })
18255 }))
18256 }
18257
18258 /// Opens a multibuffer with the given project locations in it.
18259 pub fn open_locations_in_multibuffer(
18260 workspace: &mut Workspace,
18261 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18262 title: String,
18263 split: bool,
18264 allow_preview: bool,
18265 multibuffer_selection_mode: MultibufferSelectionMode,
18266 window: &mut Window,
18267 cx: &mut Context<Workspace>,
18268 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18269 if locations.is_empty() {
18270 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18271 return None;
18272 }
18273
18274 let capability = workspace.project().read(cx).capability();
18275 let mut ranges = <Vec<Range<Anchor>>>::new();
18276
18277 // a key to find existing multibuffer editors with the same set of locations
18278 // to prevent us from opening more and more multibuffer tabs for searches and the like
18279 let mut key = (title.clone(), vec![]);
18280 let excerpt_buffer = cx.new(|cx| {
18281 let key = &mut key.1;
18282 let mut multibuffer = MultiBuffer::new(capability);
18283 for (buffer, mut ranges_for_buffer) in locations {
18284 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18285 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18286 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18287 PathKey::for_buffer(&buffer, cx),
18288 buffer.clone(),
18289 ranges_for_buffer,
18290 multibuffer_context_lines(cx),
18291 cx,
18292 );
18293 ranges.extend(new_ranges)
18294 }
18295
18296 multibuffer.with_title(title)
18297 });
18298 let existing = workspace.active_pane().update(cx, |pane, cx| {
18299 pane.items()
18300 .filter_map(|item| item.downcast::<Editor>())
18301 .find(|editor| {
18302 editor
18303 .read(cx)
18304 .lookup_key
18305 .as_ref()
18306 .and_then(|it| {
18307 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18308 })
18309 .is_some_and(|it| *it == key)
18310 })
18311 });
18312 let was_existing = existing.is_some();
18313 let editor = existing.unwrap_or_else(|| {
18314 cx.new(|cx| {
18315 let mut editor = Editor::for_multibuffer(
18316 excerpt_buffer,
18317 Some(workspace.project().clone()),
18318 window,
18319 cx,
18320 );
18321 editor.lookup_key = Some(Box::new(key));
18322 editor
18323 })
18324 });
18325 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18326 MultibufferSelectionMode::First => {
18327 if let Some(first_range) = ranges.first() {
18328 editor.change_selections(
18329 SelectionEffects::no_scroll(),
18330 window,
18331 cx,
18332 |selections| {
18333 selections.clear_disjoint();
18334 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18335 },
18336 );
18337 }
18338 editor.highlight_background::<Self>(
18339 &ranges,
18340 |_, theme| theme.colors().editor_highlighted_line_background,
18341 cx,
18342 );
18343 }
18344 MultibufferSelectionMode::All => {
18345 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18346 selections.clear_disjoint();
18347 selections.select_anchor_ranges(ranges);
18348 });
18349 }
18350 });
18351
18352 let item = Box::new(editor.clone());
18353
18354 let pane = if split {
18355 workspace.adjacent_pane(window, cx)
18356 } else {
18357 workspace.active_pane().clone()
18358 };
18359 let activate_pane = split;
18360
18361 let mut destination_index = None;
18362 pane.update(cx, |pane, cx| {
18363 if allow_preview && !was_existing {
18364 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18365 }
18366 if was_existing && !allow_preview {
18367 pane.unpreview_item_if_preview(item.item_id());
18368 }
18369 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18370 });
18371
18372 Some((editor, pane))
18373 }
18374
18375 pub fn rename(
18376 &mut self,
18377 _: &Rename,
18378 window: &mut Window,
18379 cx: &mut Context<Self>,
18380 ) -> Option<Task<Result<()>>> {
18381 use language::ToOffset as _;
18382
18383 let provider = self.semantics_provider.clone()?;
18384 let selection = self.selections.newest_anchor().clone();
18385 let (cursor_buffer, cursor_buffer_position) = self
18386 .buffer
18387 .read(cx)
18388 .text_anchor_for_position(selection.head(), cx)?;
18389 let (tail_buffer, cursor_buffer_position_end) = self
18390 .buffer
18391 .read(cx)
18392 .text_anchor_for_position(selection.tail(), cx)?;
18393 if tail_buffer != cursor_buffer {
18394 return None;
18395 }
18396
18397 let snapshot = cursor_buffer.read(cx).snapshot();
18398 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18399 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18400 let prepare_rename = provider
18401 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18402 .unwrap_or_else(|| Task::ready(Ok(None)));
18403 drop(snapshot);
18404
18405 Some(cx.spawn_in(window, async move |this, cx| {
18406 let rename_range = if let Some(range) = prepare_rename.await? {
18407 Some(range)
18408 } else {
18409 this.update(cx, |this, cx| {
18410 let buffer = this.buffer.read(cx).snapshot(cx);
18411 let mut buffer_highlights = this
18412 .document_highlights_for_position(selection.head(), &buffer)
18413 .filter(|highlight| {
18414 highlight.start.excerpt_id == selection.head().excerpt_id
18415 && highlight.end.excerpt_id == selection.head().excerpt_id
18416 });
18417 buffer_highlights
18418 .next()
18419 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18420 })?
18421 };
18422 if let Some(rename_range) = rename_range {
18423 this.update_in(cx, |this, window, cx| {
18424 let snapshot = cursor_buffer.read(cx).snapshot();
18425 let rename_buffer_range = rename_range.to_offset(&snapshot);
18426 let cursor_offset_in_rename_range =
18427 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18428 let cursor_offset_in_rename_range_end =
18429 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18430
18431 this.take_rename(false, window, cx);
18432 let buffer = this.buffer.read(cx).read(cx);
18433 let cursor_offset = selection.head().to_offset(&buffer);
18434 let rename_start =
18435 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18436 let rename_end = rename_start + rename_buffer_range.len();
18437 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18438 let mut old_highlight_id = None;
18439 let old_name: Arc<str> = buffer
18440 .chunks(rename_start..rename_end, true)
18441 .map(|chunk| {
18442 if old_highlight_id.is_none() {
18443 old_highlight_id = chunk.syntax_highlight_id;
18444 }
18445 chunk.text
18446 })
18447 .collect::<String>()
18448 .into();
18449
18450 drop(buffer);
18451
18452 // Position the selection in the rename editor so that it matches the current selection.
18453 this.show_local_selections = false;
18454 let rename_editor = cx.new(|cx| {
18455 let mut editor = Editor::single_line(window, cx);
18456 editor.buffer.update(cx, |buffer, cx| {
18457 buffer.edit(
18458 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18459 None,
18460 cx,
18461 )
18462 });
18463 let cursor_offset_in_rename_range =
18464 MultiBufferOffset(cursor_offset_in_rename_range);
18465 let cursor_offset_in_rename_range_end =
18466 MultiBufferOffset(cursor_offset_in_rename_range_end);
18467 let rename_selection_range = match cursor_offset_in_rename_range
18468 .cmp(&cursor_offset_in_rename_range_end)
18469 {
18470 Ordering::Equal => {
18471 editor.select_all(&SelectAll, window, cx);
18472 return editor;
18473 }
18474 Ordering::Less => {
18475 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18476 }
18477 Ordering::Greater => {
18478 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18479 }
18480 };
18481 if rename_selection_range.end.0 > old_name.len() {
18482 editor.select_all(&SelectAll, window, cx);
18483 } else {
18484 editor.change_selections(Default::default(), window, cx, |s| {
18485 s.select_ranges([rename_selection_range]);
18486 });
18487 }
18488 editor
18489 });
18490 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18491 if e == &EditorEvent::Focused {
18492 cx.emit(EditorEvent::FocusedIn)
18493 }
18494 })
18495 .detach();
18496
18497 let write_highlights =
18498 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18499 let read_highlights =
18500 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18501 let ranges = write_highlights
18502 .iter()
18503 .flat_map(|(_, ranges)| ranges.iter())
18504 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18505 .cloned()
18506 .collect();
18507
18508 this.highlight_text::<Rename>(
18509 ranges,
18510 HighlightStyle {
18511 fade_out: Some(0.6),
18512 ..Default::default()
18513 },
18514 cx,
18515 );
18516 let rename_focus_handle = rename_editor.focus_handle(cx);
18517 window.focus(&rename_focus_handle, cx);
18518 let block_id = this.insert_blocks(
18519 [BlockProperties {
18520 style: BlockStyle::Flex,
18521 placement: BlockPlacement::Below(range.start),
18522 height: Some(1),
18523 render: Arc::new({
18524 let rename_editor = rename_editor.clone();
18525 move |cx: &mut BlockContext| {
18526 let mut text_style = cx.editor_style.text.clone();
18527 if let Some(highlight_style) = old_highlight_id
18528 .and_then(|h| h.style(&cx.editor_style.syntax))
18529 {
18530 text_style = text_style.highlight(highlight_style);
18531 }
18532 div()
18533 .block_mouse_except_scroll()
18534 .pl(cx.anchor_x)
18535 .child(EditorElement::new(
18536 &rename_editor,
18537 EditorStyle {
18538 background: cx.theme().system().transparent,
18539 local_player: cx.editor_style.local_player,
18540 text: text_style,
18541 scrollbar_width: cx.editor_style.scrollbar_width,
18542 syntax: cx.editor_style.syntax.clone(),
18543 status: cx.editor_style.status.clone(),
18544 inlay_hints_style: HighlightStyle {
18545 font_weight: Some(FontWeight::BOLD),
18546 ..make_inlay_hints_style(cx.app)
18547 },
18548 edit_prediction_styles: make_suggestion_styles(
18549 cx.app,
18550 ),
18551 ..EditorStyle::default()
18552 },
18553 ))
18554 .into_any_element()
18555 }
18556 }),
18557 priority: 0,
18558 }],
18559 Some(Autoscroll::fit()),
18560 cx,
18561 )[0];
18562 this.pending_rename = Some(RenameState {
18563 range,
18564 old_name,
18565 editor: rename_editor,
18566 block_id,
18567 });
18568 })?;
18569 }
18570
18571 Ok(())
18572 }))
18573 }
18574
18575 pub fn confirm_rename(
18576 &mut self,
18577 _: &ConfirmRename,
18578 window: &mut Window,
18579 cx: &mut Context<Self>,
18580 ) -> Option<Task<Result<()>>> {
18581 let rename = self.take_rename(false, window, cx)?;
18582 let workspace = self.workspace()?.downgrade();
18583 let (buffer, start) = self
18584 .buffer
18585 .read(cx)
18586 .text_anchor_for_position(rename.range.start, cx)?;
18587 let (end_buffer, _) = self
18588 .buffer
18589 .read(cx)
18590 .text_anchor_for_position(rename.range.end, cx)?;
18591 if buffer != end_buffer {
18592 return None;
18593 }
18594
18595 let old_name = rename.old_name;
18596 let new_name = rename.editor.read(cx).text(cx);
18597
18598 let rename = self.semantics_provider.as_ref()?.perform_rename(
18599 &buffer,
18600 start,
18601 new_name.clone(),
18602 cx,
18603 )?;
18604
18605 Some(cx.spawn_in(window, async move |editor, cx| {
18606 let project_transaction = rename.await?;
18607 Self::open_project_transaction(
18608 &editor,
18609 workspace,
18610 project_transaction,
18611 format!("Rename: {} → {}", old_name, new_name),
18612 cx,
18613 )
18614 .await?;
18615
18616 editor.update(cx, |editor, cx| {
18617 editor.refresh_document_highlights(cx);
18618 })?;
18619 Ok(())
18620 }))
18621 }
18622
18623 fn take_rename(
18624 &mut self,
18625 moving_cursor: bool,
18626 window: &mut Window,
18627 cx: &mut Context<Self>,
18628 ) -> Option<RenameState> {
18629 let rename = self.pending_rename.take()?;
18630 if rename.editor.focus_handle(cx).is_focused(window) {
18631 window.focus(&self.focus_handle, cx);
18632 }
18633
18634 self.remove_blocks(
18635 [rename.block_id].into_iter().collect(),
18636 Some(Autoscroll::fit()),
18637 cx,
18638 );
18639 self.clear_highlights::<Rename>(cx);
18640 self.show_local_selections = true;
18641
18642 if moving_cursor {
18643 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18644 editor
18645 .selections
18646 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18647 .head()
18648 });
18649
18650 // Update the selection to match the position of the selection inside
18651 // the rename editor.
18652 let snapshot = self.buffer.read(cx).read(cx);
18653 let rename_range = rename.range.to_offset(&snapshot);
18654 let cursor_in_editor = snapshot
18655 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18656 .min(rename_range.end);
18657 drop(snapshot);
18658
18659 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18660 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18661 });
18662 } else {
18663 self.refresh_document_highlights(cx);
18664 }
18665
18666 Some(rename)
18667 }
18668
18669 pub fn pending_rename(&self) -> Option<&RenameState> {
18670 self.pending_rename.as_ref()
18671 }
18672
18673 fn format(
18674 &mut self,
18675 _: &Format,
18676 window: &mut Window,
18677 cx: &mut Context<Self>,
18678 ) -> Option<Task<Result<()>>> {
18679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18680
18681 let project = match &self.project {
18682 Some(project) => project.clone(),
18683 None => return None,
18684 };
18685
18686 Some(self.perform_format(
18687 project,
18688 FormatTrigger::Manual,
18689 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18690 window,
18691 cx,
18692 ))
18693 }
18694
18695 fn format_selections(
18696 &mut self,
18697 _: &FormatSelections,
18698 window: &mut Window,
18699 cx: &mut Context<Self>,
18700 ) -> Option<Task<Result<()>>> {
18701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18702
18703 let project = match &self.project {
18704 Some(project) => project.clone(),
18705 None => return None,
18706 };
18707
18708 let ranges = self
18709 .selections
18710 .all_adjusted(&self.display_snapshot(cx))
18711 .into_iter()
18712 .map(|selection| selection.range())
18713 .collect_vec();
18714
18715 Some(self.perform_format(
18716 project,
18717 FormatTrigger::Manual,
18718 FormatTarget::Ranges(ranges),
18719 window,
18720 cx,
18721 ))
18722 }
18723
18724 fn perform_format(
18725 &mut self,
18726 project: Entity<Project>,
18727 trigger: FormatTrigger,
18728 target: FormatTarget,
18729 window: &mut Window,
18730 cx: &mut Context<Self>,
18731 ) -> Task<Result<()>> {
18732 let buffer = self.buffer.clone();
18733 let (buffers, target) = match target {
18734 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18735 FormatTarget::Ranges(selection_ranges) => {
18736 let multi_buffer = buffer.read(cx);
18737 let snapshot = multi_buffer.read(cx);
18738 let mut buffers = HashSet::default();
18739 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18740 BTreeMap::new();
18741 for selection_range in selection_ranges {
18742 for (buffer, buffer_range, _) in
18743 snapshot.range_to_buffer_ranges(selection_range)
18744 {
18745 let buffer_id = buffer.remote_id();
18746 let start = buffer.anchor_before(buffer_range.start);
18747 let end = buffer.anchor_after(buffer_range.end);
18748 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18749 buffer_id_to_ranges
18750 .entry(buffer_id)
18751 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18752 .or_insert_with(|| vec![start..end]);
18753 }
18754 }
18755 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18756 }
18757 };
18758
18759 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18760 let selections_prev = transaction_id_prev
18761 .and_then(|transaction_id_prev| {
18762 // default to selections as they were after the last edit, if we have them,
18763 // instead of how they are now.
18764 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18765 // will take you back to where you made the last edit, instead of staying where you scrolled
18766 self.selection_history
18767 .transaction(transaction_id_prev)
18768 .map(|t| t.0.clone())
18769 })
18770 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18771
18772 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18773 let format = project.update(cx, |project, cx| {
18774 project.format(buffers, target, true, trigger, cx)
18775 });
18776
18777 cx.spawn_in(window, async move |editor, cx| {
18778 let transaction = futures::select_biased! {
18779 transaction = format.log_err().fuse() => transaction,
18780 () = timeout => {
18781 log::warn!("timed out waiting for formatting");
18782 None
18783 }
18784 };
18785
18786 buffer.update(cx, |buffer, cx| {
18787 if let Some(transaction) = transaction
18788 && !buffer.is_singleton()
18789 {
18790 buffer.push_transaction(&transaction.0, cx);
18791 }
18792 cx.notify();
18793 });
18794
18795 if let Some(transaction_id_now) =
18796 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18797 {
18798 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18799 if has_new_transaction {
18800 editor
18801 .update(cx, |editor, _| {
18802 editor
18803 .selection_history
18804 .insert_transaction(transaction_id_now, selections_prev);
18805 })
18806 .ok();
18807 }
18808 }
18809
18810 Ok(())
18811 })
18812 }
18813
18814 fn organize_imports(
18815 &mut self,
18816 _: &OrganizeImports,
18817 window: &mut Window,
18818 cx: &mut Context<Self>,
18819 ) -> Option<Task<Result<()>>> {
18820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18821 let project = match &self.project {
18822 Some(project) => project.clone(),
18823 None => return None,
18824 };
18825 Some(self.perform_code_action_kind(
18826 project,
18827 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18828 window,
18829 cx,
18830 ))
18831 }
18832
18833 fn perform_code_action_kind(
18834 &mut self,
18835 project: Entity<Project>,
18836 kind: CodeActionKind,
18837 window: &mut Window,
18838 cx: &mut Context<Self>,
18839 ) -> Task<Result<()>> {
18840 let buffer = self.buffer.clone();
18841 let buffers = buffer.read(cx).all_buffers();
18842 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18843 let apply_action = project.update(cx, |project, cx| {
18844 project.apply_code_action_kind(buffers, kind, true, cx)
18845 });
18846 cx.spawn_in(window, async move |_, cx| {
18847 let transaction = futures::select_biased! {
18848 () = timeout => {
18849 log::warn!("timed out waiting for executing code action");
18850 None
18851 }
18852 transaction = apply_action.log_err().fuse() => transaction,
18853 };
18854 buffer.update(cx, |buffer, cx| {
18855 // check if we need this
18856 if let Some(transaction) = transaction
18857 && !buffer.is_singleton()
18858 {
18859 buffer.push_transaction(&transaction.0, cx);
18860 }
18861 cx.notify();
18862 });
18863 Ok(())
18864 })
18865 }
18866
18867 pub fn restart_language_server(
18868 &mut self,
18869 _: &RestartLanguageServer,
18870 _: &mut Window,
18871 cx: &mut Context<Self>,
18872 ) {
18873 if let Some(project) = self.project.clone() {
18874 self.buffer.update(cx, |multi_buffer, cx| {
18875 project.update(cx, |project, cx| {
18876 project.restart_language_servers_for_buffers(
18877 multi_buffer.all_buffers().into_iter().collect(),
18878 HashSet::default(),
18879 cx,
18880 );
18881 });
18882 })
18883 }
18884 }
18885
18886 pub fn stop_language_server(
18887 &mut self,
18888 _: &StopLanguageServer,
18889 _: &mut Window,
18890 cx: &mut Context<Self>,
18891 ) {
18892 if let Some(project) = self.project.clone() {
18893 self.buffer.update(cx, |multi_buffer, cx| {
18894 project.update(cx, |project, cx| {
18895 project.stop_language_servers_for_buffers(
18896 multi_buffer.all_buffers().into_iter().collect(),
18897 HashSet::default(),
18898 cx,
18899 );
18900 });
18901 });
18902 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18903 }
18904 }
18905
18906 fn cancel_language_server_work(
18907 workspace: &mut Workspace,
18908 _: &actions::CancelLanguageServerWork,
18909 _: &mut Window,
18910 cx: &mut Context<Workspace>,
18911 ) {
18912 let project = workspace.project();
18913 let buffers = workspace
18914 .active_item(cx)
18915 .and_then(|item| item.act_as::<Editor>(cx))
18916 .map_or(HashSet::default(), |editor| {
18917 editor.read(cx).buffer.read(cx).all_buffers()
18918 });
18919 project.update(cx, |project, cx| {
18920 project.cancel_language_server_work_for_buffers(buffers, cx);
18921 });
18922 }
18923
18924 fn show_character_palette(
18925 &mut self,
18926 _: &ShowCharacterPalette,
18927 window: &mut Window,
18928 _: &mut Context<Self>,
18929 ) {
18930 window.show_character_palette();
18931 }
18932
18933 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18934 if !self.diagnostics_enabled() {
18935 return;
18936 }
18937
18938 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18939 let buffer = self.buffer.read(cx).snapshot(cx);
18940 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18941 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18942 let is_valid = buffer
18943 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18944 .any(|entry| {
18945 entry.diagnostic.is_primary
18946 && !entry.range.is_empty()
18947 && entry.range.start == primary_range_start
18948 && entry.diagnostic.message == active_diagnostics.active_message
18949 });
18950
18951 if !is_valid {
18952 self.dismiss_diagnostics(cx);
18953 }
18954 }
18955 }
18956
18957 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18958 match &self.active_diagnostics {
18959 ActiveDiagnostic::Group(group) => Some(group),
18960 _ => None,
18961 }
18962 }
18963
18964 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18965 if !self.diagnostics_enabled() {
18966 return;
18967 }
18968 self.dismiss_diagnostics(cx);
18969 self.active_diagnostics = ActiveDiagnostic::All;
18970 }
18971
18972 fn activate_diagnostics(
18973 &mut self,
18974 buffer_id: BufferId,
18975 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18976 window: &mut Window,
18977 cx: &mut Context<Self>,
18978 ) {
18979 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18980 return;
18981 }
18982 self.dismiss_diagnostics(cx);
18983 let snapshot = self.snapshot(window, cx);
18984 let buffer = self.buffer.read(cx).snapshot(cx);
18985 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18986 return;
18987 };
18988
18989 let diagnostic_group = buffer
18990 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18991 .collect::<Vec<_>>();
18992
18993 let language_registry = self
18994 .project()
18995 .map(|project| project.read(cx).languages().clone());
18996
18997 let blocks = renderer.render_group(
18998 diagnostic_group,
18999 buffer_id,
19000 snapshot,
19001 cx.weak_entity(),
19002 language_registry,
19003 cx,
19004 );
19005
19006 let blocks = self.display_map.update(cx, |display_map, cx| {
19007 display_map.insert_blocks(blocks, cx).into_iter().collect()
19008 });
19009 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19010 active_range: buffer.anchor_before(diagnostic.range.start)
19011 ..buffer.anchor_after(diagnostic.range.end),
19012 active_message: diagnostic.diagnostic.message.clone(),
19013 group_id: diagnostic.diagnostic.group_id,
19014 blocks,
19015 });
19016 cx.notify();
19017 }
19018
19019 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19020 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19021 return;
19022 };
19023
19024 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19025 if let ActiveDiagnostic::Group(group) = prev {
19026 self.display_map.update(cx, |display_map, cx| {
19027 display_map.remove_blocks(group.blocks, cx);
19028 });
19029 cx.notify();
19030 }
19031 }
19032
19033 /// Disable inline diagnostics rendering for this editor.
19034 pub fn disable_inline_diagnostics(&mut self) {
19035 self.inline_diagnostics_enabled = false;
19036 self.inline_diagnostics_update = Task::ready(());
19037 self.inline_diagnostics.clear();
19038 }
19039
19040 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19041 self.diagnostics_enabled = false;
19042 self.dismiss_diagnostics(cx);
19043 self.inline_diagnostics_update = Task::ready(());
19044 self.inline_diagnostics.clear();
19045 }
19046
19047 pub fn disable_word_completions(&mut self) {
19048 self.word_completions_enabled = false;
19049 }
19050
19051 pub fn diagnostics_enabled(&self) -> bool {
19052 self.diagnostics_enabled && self.mode.is_full()
19053 }
19054
19055 pub fn inline_diagnostics_enabled(&self) -> bool {
19056 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19057 }
19058
19059 pub fn show_inline_diagnostics(&self) -> bool {
19060 self.show_inline_diagnostics
19061 }
19062
19063 pub fn toggle_inline_diagnostics(
19064 &mut self,
19065 _: &ToggleInlineDiagnostics,
19066 window: &mut Window,
19067 cx: &mut Context<Editor>,
19068 ) {
19069 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19070 self.refresh_inline_diagnostics(false, window, cx);
19071 }
19072
19073 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19074 self.diagnostics_max_severity = severity;
19075 self.display_map.update(cx, |display_map, _| {
19076 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19077 });
19078 }
19079
19080 pub fn toggle_diagnostics(
19081 &mut self,
19082 _: &ToggleDiagnostics,
19083 window: &mut Window,
19084 cx: &mut Context<Editor>,
19085 ) {
19086 if !self.diagnostics_enabled() {
19087 return;
19088 }
19089
19090 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19091 EditorSettings::get_global(cx)
19092 .diagnostics_max_severity
19093 .filter(|severity| severity != &DiagnosticSeverity::Off)
19094 .unwrap_or(DiagnosticSeverity::Hint)
19095 } else {
19096 DiagnosticSeverity::Off
19097 };
19098 self.set_max_diagnostics_severity(new_severity, cx);
19099 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19100 self.active_diagnostics = ActiveDiagnostic::None;
19101 self.inline_diagnostics_update = Task::ready(());
19102 self.inline_diagnostics.clear();
19103 } else {
19104 self.refresh_inline_diagnostics(false, window, cx);
19105 }
19106
19107 cx.notify();
19108 }
19109
19110 pub fn toggle_minimap(
19111 &mut self,
19112 _: &ToggleMinimap,
19113 window: &mut Window,
19114 cx: &mut Context<Editor>,
19115 ) {
19116 if self.supports_minimap(cx) {
19117 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19118 }
19119 }
19120
19121 fn refresh_inline_diagnostics(
19122 &mut self,
19123 debounce: bool,
19124 window: &mut Window,
19125 cx: &mut Context<Self>,
19126 ) {
19127 let max_severity = ProjectSettings::get_global(cx)
19128 .diagnostics
19129 .inline
19130 .max_severity
19131 .unwrap_or(self.diagnostics_max_severity);
19132
19133 if !self.inline_diagnostics_enabled()
19134 || !self.diagnostics_enabled()
19135 || !self.show_inline_diagnostics
19136 || max_severity == DiagnosticSeverity::Off
19137 {
19138 self.inline_diagnostics_update = Task::ready(());
19139 self.inline_diagnostics.clear();
19140 return;
19141 }
19142
19143 let debounce_ms = ProjectSettings::get_global(cx)
19144 .diagnostics
19145 .inline
19146 .update_debounce_ms;
19147 let debounce = if debounce && debounce_ms > 0 {
19148 Some(Duration::from_millis(debounce_ms))
19149 } else {
19150 None
19151 };
19152 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19153 if let Some(debounce) = debounce {
19154 cx.background_executor().timer(debounce).await;
19155 }
19156 let Some(snapshot) = editor.upgrade().map(|editor| {
19157 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19158 }) else {
19159 return;
19160 };
19161
19162 let new_inline_diagnostics = cx
19163 .background_spawn(async move {
19164 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19165 for diagnostic_entry in
19166 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19167 {
19168 let message = diagnostic_entry
19169 .diagnostic
19170 .message
19171 .split_once('\n')
19172 .map(|(line, _)| line)
19173 .map(SharedString::new)
19174 .unwrap_or_else(|| {
19175 SharedString::new(&*diagnostic_entry.diagnostic.message)
19176 });
19177 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19178 let (Ok(i) | Err(i)) = inline_diagnostics
19179 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19180 inline_diagnostics.insert(
19181 i,
19182 (
19183 start_anchor,
19184 InlineDiagnostic {
19185 message,
19186 group_id: diagnostic_entry.diagnostic.group_id,
19187 start: diagnostic_entry.range.start.to_point(&snapshot),
19188 is_primary: diagnostic_entry.diagnostic.is_primary,
19189 severity: diagnostic_entry.diagnostic.severity,
19190 },
19191 ),
19192 );
19193 }
19194 inline_diagnostics
19195 })
19196 .await;
19197
19198 editor
19199 .update(cx, |editor, cx| {
19200 editor.inline_diagnostics = new_inline_diagnostics;
19201 cx.notify();
19202 })
19203 .ok();
19204 });
19205 }
19206
19207 fn pull_diagnostics(
19208 &mut self,
19209 buffer_id: Option<BufferId>,
19210 window: &Window,
19211 cx: &mut Context<Self>,
19212 ) -> Option<()> {
19213 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
19214 return None;
19215 }
19216 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19217 .diagnostics
19218 .lsp_pull_diagnostics;
19219 if !pull_diagnostics_settings.enabled {
19220 return None;
19221 }
19222 let project = self.project()?.downgrade();
19223
19224 let mut edited_buffer_ids = HashSet::default();
19225 let mut edited_worktree_ids = HashSet::default();
19226 let edited_buffers = match buffer_id {
19227 Some(buffer_id) => {
19228 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19229 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
19230 edited_buffer_ids.insert(buffer.read(cx).remote_id());
19231 edited_worktree_ids.insert(worktree_id);
19232 vec![buffer]
19233 }
19234 None => self
19235 .buffer()
19236 .read(cx)
19237 .all_buffers()
19238 .into_iter()
19239 .filter(|buffer| {
19240 let buffer = buffer.read(cx);
19241 match buffer.file().map(|f| f.worktree_id(cx)) {
19242 Some(worktree_id) => {
19243 edited_buffer_ids.insert(buffer.remote_id());
19244 edited_worktree_ids.insert(worktree_id);
19245 true
19246 }
19247 None => false,
19248 }
19249 })
19250 .collect::<Vec<_>>(),
19251 };
19252
19253 if edited_buffers.is_empty() {
19254 self.pull_diagnostics_task = Task::ready(());
19255 self.pull_diagnostics_background_task = Task::ready(());
19256 return None;
19257 }
19258
19259 let mut already_used_buffers = HashSet::default();
19260 let related_open_buffers = self
19261 .workspace
19262 .as_ref()
19263 .and_then(|(workspace, _)| workspace.upgrade())
19264 .into_iter()
19265 .flat_map(|workspace| workspace.read(cx).panes())
19266 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
19267 .filter(|editor| editor != &cx.entity())
19268 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
19269 .filter(|buffer| {
19270 let buffer = buffer.read(cx);
19271 let buffer_id = buffer.remote_id();
19272 if already_used_buffers.insert(buffer_id) {
19273 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
19274 return !edited_buffer_ids.contains(&buffer_id)
19275 && edited_worktree_ids.contains(&worktree_id);
19276 }
19277 }
19278 false
19279 })
19280 .collect::<Vec<_>>();
19281
19282 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19283 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
19284 if buffers.is_empty() {
19285 return Task::ready(());
19286 }
19287 let project_weak = project.clone();
19288 cx.spawn_in(window, async move |_, cx| {
19289 cx.background_executor().timer(delay).await;
19290
19291 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
19292 buffers
19293 .into_iter()
19294 .filter_map(|buffer| {
19295 project_weak
19296 .update(cx, |project, cx| {
19297 project.lsp_store().update(cx, |lsp_store, cx| {
19298 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19299 })
19300 })
19301 .ok()
19302 })
19303 .collect::<FuturesUnordered<_>>()
19304 }) else {
19305 return;
19306 };
19307
19308 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
19309 if let Err(e) = pull_task {
19310 log::error!("Failed to update project diagnostics: {e:#}");
19311 }
19312 }
19313 })
19314 };
19315
19316 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
19317 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
19318
19319 Some(())
19320 }
19321
19322 pub fn set_selections_from_remote(
19323 &mut self,
19324 selections: Vec<Selection<Anchor>>,
19325 pending_selection: Option<Selection<Anchor>>,
19326 window: &mut Window,
19327 cx: &mut Context<Self>,
19328 ) {
19329 let old_cursor_position = self.selections.newest_anchor().head();
19330 self.selections
19331 .change_with(&self.display_snapshot(cx), |s| {
19332 s.select_anchors(selections);
19333 if let Some(pending_selection) = pending_selection {
19334 s.set_pending(pending_selection, SelectMode::Character);
19335 } else {
19336 s.clear_pending();
19337 }
19338 });
19339 self.selections_did_change(
19340 false,
19341 &old_cursor_position,
19342 SelectionEffects::default(),
19343 window,
19344 cx,
19345 );
19346 }
19347
19348 pub fn transact(
19349 &mut self,
19350 window: &mut Window,
19351 cx: &mut Context<Self>,
19352 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19353 ) -> Option<TransactionId> {
19354 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19355 this.start_transaction_at(Instant::now(), window, cx);
19356 update(this, window, cx);
19357 this.end_transaction_at(Instant::now(), cx)
19358 })
19359 }
19360
19361 pub fn start_transaction_at(
19362 &mut self,
19363 now: Instant,
19364 window: &mut Window,
19365 cx: &mut Context<Self>,
19366 ) -> Option<TransactionId> {
19367 self.end_selection(window, cx);
19368 if let Some(tx_id) = self
19369 .buffer
19370 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19371 {
19372 self.selection_history
19373 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19374 cx.emit(EditorEvent::TransactionBegun {
19375 transaction_id: tx_id,
19376 });
19377 Some(tx_id)
19378 } else {
19379 None
19380 }
19381 }
19382
19383 pub fn end_transaction_at(
19384 &mut self,
19385 now: Instant,
19386 cx: &mut Context<Self>,
19387 ) -> Option<TransactionId> {
19388 if let Some(transaction_id) = self
19389 .buffer
19390 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19391 {
19392 if let Some((_, end_selections)) =
19393 self.selection_history.transaction_mut(transaction_id)
19394 {
19395 *end_selections = Some(self.selections.disjoint_anchors_arc());
19396 } else {
19397 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19398 }
19399
19400 cx.emit(EditorEvent::Edited { transaction_id });
19401 Some(transaction_id)
19402 } else {
19403 None
19404 }
19405 }
19406
19407 pub fn modify_transaction_selection_history(
19408 &mut self,
19409 transaction_id: TransactionId,
19410 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19411 ) -> bool {
19412 self.selection_history
19413 .transaction_mut(transaction_id)
19414 .map(modify)
19415 .is_some()
19416 }
19417
19418 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19419 if self.selection_mark_mode {
19420 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19421 s.move_with(|_, sel| {
19422 sel.collapse_to(sel.head(), SelectionGoal::None);
19423 });
19424 })
19425 }
19426 self.selection_mark_mode = true;
19427 cx.notify();
19428 }
19429
19430 pub fn swap_selection_ends(
19431 &mut self,
19432 _: &actions::SwapSelectionEnds,
19433 window: &mut Window,
19434 cx: &mut Context<Self>,
19435 ) {
19436 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19437 s.move_with(|_, sel| {
19438 if sel.start != sel.end {
19439 sel.reversed = !sel.reversed
19440 }
19441 });
19442 });
19443 self.request_autoscroll(Autoscroll::newest(), cx);
19444 cx.notify();
19445 }
19446
19447 pub fn toggle_focus(
19448 workspace: &mut Workspace,
19449 _: &actions::ToggleFocus,
19450 window: &mut Window,
19451 cx: &mut Context<Workspace>,
19452 ) {
19453 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19454 return;
19455 };
19456 workspace.activate_item(&item, true, true, window, cx);
19457 }
19458
19459 pub fn toggle_fold(
19460 &mut self,
19461 _: &actions::ToggleFold,
19462 window: &mut Window,
19463 cx: &mut Context<Self>,
19464 ) {
19465 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19467 let selection = self.selections.newest::<Point>(&display_map);
19468
19469 let range = if selection.is_empty() {
19470 let point = selection.head().to_display_point(&display_map);
19471 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19472 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19473 .to_point(&display_map);
19474 start..end
19475 } else {
19476 selection.range()
19477 };
19478 if display_map.folds_in_range(range).next().is_some() {
19479 self.unfold_lines(&Default::default(), window, cx)
19480 } else {
19481 self.fold(&Default::default(), window, cx)
19482 }
19483 } else {
19484 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19485 let buffer_ids: HashSet<_> = self
19486 .selections
19487 .disjoint_anchor_ranges()
19488 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19489 .collect();
19490
19491 let should_unfold = buffer_ids
19492 .iter()
19493 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19494
19495 for buffer_id in buffer_ids {
19496 if should_unfold {
19497 self.unfold_buffer(buffer_id, cx);
19498 } else {
19499 self.fold_buffer(buffer_id, cx);
19500 }
19501 }
19502 }
19503 }
19504
19505 pub fn toggle_fold_recursive(
19506 &mut self,
19507 _: &actions::ToggleFoldRecursive,
19508 window: &mut Window,
19509 cx: &mut Context<Self>,
19510 ) {
19511 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19512
19513 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19514 let range = if selection.is_empty() {
19515 let point = selection.head().to_display_point(&display_map);
19516 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19517 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19518 .to_point(&display_map);
19519 start..end
19520 } else {
19521 selection.range()
19522 };
19523 if display_map.folds_in_range(range).next().is_some() {
19524 self.unfold_recursive(&Default::default(), window, cx)
19525 } else {
19526 self.fold_recursive(&Default::default(), window, cx)
19527 }
19528 }
19529
19530 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19531 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19532 let mut to_fold = Vec::new();
19533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19534 let selections = self.selections.all_adjusted(&display_map);
19535
19536 for selection in selections {
19537 let range = selection.range().sorted();
19538 let buffer_start_row = range.start.row;
19539
19540 if range.start.row != range.end.row {
19541 let mut found = false;
19542 let mut row = range.start.row;
19543 while row <= range.end.row {
19544 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19545 {
19546 found = true;
19547 row = crease.range().end.row + 1;
19548 to_fold.push(crease);
19549 } else {
19550 row += 1
19551 }
19552 }
19553 if found {
19554 continue;
19555 }
19556 }
19557
19558 for row in (0..=range.start.row).rev() {
19559 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19560 && crease.range().end.row >= buffer_start_row
19561 {
19562 to_fold.push(crease);
19563 if row <= range.start.row {
19564 break;
19565 }
19566 }
19567 }
19568 }
19569
19570 self.fold_creases(to_fold, true, window, cx);
19571 } else {
19572 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19573 let buffer_ids = self
19574 .selections
19575 .disjoint_anchor_ranges()
19576 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19577 .collect::<HashSet<_>>();
19578 for buffer_id in buffer_ids {
19579 self.fold_buffer(buffer_id, cx);
19580 }
19581 }
19582 }
19583
19584 pub fn toggle_fold_all(
19585 &mut self,
19586 _: &actions::ToggleFoldAll,
19587 window: &mut Window,
19588 cx: &mut Context<Self>,
19589 ) {
19590 let has_folds = if self.buffer.read(cx).is_singleton() {
19591 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19592 let has_folds = display_map
19593 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19594 .next()
19595 .is_some();
19596 has_folds
19597 } else {
19598 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19599 let has_folds = buffer_ids
19600 .iter()
19601 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19602 has_folds
19603 };
19604
19605 if has_folds {
19606 self.unfold_all(&actions::UnfoldAll, window, cx);
19607 } else {
19608 self.fold_all(&actions::FoldAll, window, cx);
19609 }
19610 }
19611
19612 fn fold_at_level(
19613 &mut self,
19614 fold_at: &FoldAtLevel,
19615 window: &mut Window,
19616 cx: &mut Context<Self>,
19617 ) {
19618 if !self.buffer.read(cx).is_singleton() {
19619 return;
19620 }
19621
19622 let fold_at_level = fold_at.0;
19623 let snapshot = self.buffer.read(cx).snapshot(cx);
19624 let mut to_fold = Vec::new();
19625 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19626
19627 let row_ranges_to_keep: Vec<Range<u32>> = self
19628 .selections
19629 .all::<Point>(&self.display_snapshot(cx))
19630 .into_iter()
19631 .map(|sel| sel.start.row..sel.end.row)
19632 .collect();
19633
19634 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19635 while start_row < end_row {
19636 match self
19637 .snapshot(window, cx)
19638 .crease_for_buffer_row(MultiBufferRow(start_row))
19639 {
19640 Some(crease) => {
19641 let nested_start_row = crease.range().start.row + 1;
19642 let nested_end_row = crease.range().end.row;
19643
19644 if current_level < fold_at_level {
19645 stack.push((nested_start_row, nested_end_row, current_level + 1));
19646 } else if current_level == fold_at_level {
19647 // Fold iff there is no selection completely contained within the fold region
19648 if !row_ranges_to_keep.iter().any(|selection| {
19649 selection.end >= nested_start_row
19650 && selection.start <= nested_end_row
19651 }) {
19652 to_fold.push(crease);
19653 }
19654 }
19655
19656 start_row = nested_end_row + 1;
19657 }
19658 None => start_row += 1,
19659 }
19660 }
19661 }
19662
19663 self.fold_creases(to_fold, true, window, cx);
19664 }
19665
19666 pub fn fold_at_level_1(
19667 &mut self,
19668 _: &actions::FoldAtLevel1,
19669 window: &mut Window,
19670 cx: &mut Context<Self>,
19671 ) {
19672 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19673 }
19674
19675 pub fn fold_at_level_2(
19676 &mut self,
19677 _: &actions::FoldAtLevel2,
19678 window: &mut Window,
19679 cx: &mut Context<Self>,
19680 ) {
19681 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19682 }
19683
19684 pub fn fold_at_level_3(
19685 &mut self,
19686 _: &actions::FoldAtLevel3,
19687 window: &mut Window,
19688 cx: &mut Context<Self>,
19689 ) {
19690 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19691 }
19692
19693 pub fn fold_at_level_4(
19694 &mut self,
19695 _: &actions::FoldAtLevel4,
19696 window: &mut Window,
19697 cx: &mut Context<Self>,
19698 ) {
19699 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19700 }
19701
19702 pub fn fold_at_level_5(
19703 &mut self,
19704 _: &actions::FoldAtLevel5,
19705 window: &mut Window,
19706 cx: &mut Context<Self>,
19707 ) {
19708 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19709 }
19710
19711 pub fn fold_at_level_6(
19712 &mut self,
19713 _: &actions::FoldAtLevel6,
19714 window: &mut Window,
19715 cx: &mut Context<Self>,
19716 ) {
19717 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19718 }
19719
19720 pub fn fold_at_level_7(
19721 &mut self,
19722 _: &actions::FoldAtLevel7,
19723 window: &mut Window,
19724 cx: &mut Context<Self>,
19725 ) {
19726 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19727 }
19728
19729 pub fn fold_at_level_8(
19730 &mut self,
19731 _: &actions::FoldAtLevel8,
19732 window: &mut Window,
19733 cx: &mut Context<Self>,
19734 ) {
19735 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19736 }
19737
19738 pub fn fold_at_level_9(
19739 &mut self,
19740 _: &actions::FoldAtLevel9,
19741 window: &mut Window,
19742 cx: &mut Context<Self>,
19743 ) {
19744 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19745 }
19746
19747 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19748 if self.buffer.read(cx).is_singleton() {
19749 let mut fold_ranges = Vec::new();
19750 let snapshot = self.buffer.read(cx).snapshot(cx);
19751
19752 for row in 0..snapshot.max_row().0 {
19753 if let Some(foldable_range) = self
19754 .snapshot(window, cx)
19755 .crease_for_buffer_row(MultiBufferRow(row))
19756 {
19757 fold_ranges.push(foldable_range);
19758 }
19759 }
19760
19761 self.fold_creases(fold_ranges, true, window, cx);
19762 } else {
19763 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19764 editor
19765 .update_in(cx, |editor, _, cx| {
19766 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19767 editor.fold_buffer(buffer_id, cx);
19768 }
19769 })
19770 .ok();
19771 });
19772 }
19773 cx.emit(SearchEvent::ResultsCollapsedChanged(
19774 CollapseDirection::Collapsed,
19775 ));
19776 }
19777
19778 pub fn fold_function_bodies(
19779 &mut self,
19780 _: &actions::FoldFunctionBodies,
19781 window: &mut Window,
19782 cx: &mut Context<Self>,
19783 ) {
19784 let snapshot = self.buffer.read(cx).snapshot(cx);
19785
19786 let ranges = snapshot
19787 .text_object_ranges(
19788 MultiBufferOffset(0)..snapshot.len(),
19789 TreeSitterOptions::default(),
19790 )
19791 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19792 .collect::<Vec<_>>();
19793
19794 let creases = ranges
19795 .into_iter()
19796 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19797 .collect();
19798
19799 self.fold_creases(creases, true, window, cx);
19800 }
19801
19802 pub fn fold_recursive(
19803 &mut self,
19804 _: &actions::FoldRecursive,
19805 window: &mut Window,
19806 cx: &mut Context<Self>,
19807 ) {
19808 let mut to_fold = Vec::new();
19809 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19810 let selections = self.selections.all_adjusted(&display_map);
19811
19812 for selection in selections {
19813 let range = selection.range().sorted();
19814 let buffer_start_row = range.start.row;
19815
19816 if range.start.row != range.end.row {
19817 let mut found = false;
19818 for row in range.start.row..=range.end.row {
19819 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19820 found = true;
19821 to_fold.push(crease);
19822 }
19823 }
19824 if found {
19825 continue;
19826 }
19827 }
19828
19829 for row in (0..=range.start.row).rev() {
19830 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19831 if crease.range().end.row >= buffer_start_row {
19832 to_fold.push(crease);
19833 } else {
19834 break;
19835 }
19836 }
19837 }
19838 }
19839
19840 self.fold_creases(to_fold, true, window, cx);
19841 }
19842
19843 pub fn fold_at(
19844 &mut self,
19845 buffer_row: MultiBufferRow,
19846 window: &mut Window,
19847 cx: &mut Context<Self>,
19848 ) {
19849 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19850
19851 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19852 let autoscroll = self
19853 .selections
19854 .all::<Point>(&display_map)
19855 .iter()
19856 .any(|selection| crease.range().overlaps(&selection.range()));
19857
19858 self.fold_creases(vec![crease], autoscroll, window, cx);
19859 }
19860 }
19861
19862 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19863 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19864 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19865 let buffer = display_map.buffer_snapshot();
19866 let selections = self.selections.all::<Point>(&display_map);
19867 let ranges = selections
19868 .iter()
19869 .map(|s| {
19870 let range = s.display_range(&display_map).sorted();
19871 let mut start = range.start.to_point(&display_map);
19872 let mut end = range.end.to_point(&display_map);
19873 start.column = 0;
19874 end.column = buffer.line_len(MultiBufferRow(end.row));
19875 start..end
19876 })
19877 .collect::<Vec<_>>();
19878
19879 self.unfold_ranges(&ranges, true, true, cx);
19880 } else {
19881 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19882 let buffer_ids = self
19883 .selections
19884 .disjoint_anchor_ranges()
19885 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19886 .collect::<HashSet<_>>();
19887 for buffer_id in buffer_ids {
19888 self.unfold_buffer(buffer_id, cx);
19889 }
19890 }
19891 }
19892
19893 pub fn unfold_recursive(
19894 &mut self,
19895 _: &UnfoldRecursive,
19896 _window: &mut Window,
19897 cx: &mut Context<Self>,
19898 ) {
19899 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19900 let selections = self.selections.all::<Point>(&display_map);
19901 let ranges = selections
19902 .iter()
19903 .map(|s| {
19904 let mut range = s.display_range(&display_map).sorted();
19905 *range.start.column_mut() = 0;
19906 *range.end.column_mut() = display_map.line_len(range.end.row());
19907 let start = range.start.to_point(&display_map);
19908 let end = range.end.to_point(&display_map);
19909 start..end
19910 })
19911 .collect::<Vec<_>>();
19912
19913 self.unfold_ranges(&ranges, true, true, cx);
19914 }
19915
19916 pub fn unfold_at(
19917 &mut self,
19918 buffer_row: MultiBufferRow,
19919 _window: &mut Window,
19920 cx: &mut Context<Self>,
19921 ) {
19922 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19923
19924 let intersection_range = Point::new(buffer_row.0, 0)
19925 ..Point::new(
19926 buffer_row.0,
19927 display_map.buffer_snapshot().line_len(buffer_row),
19928 );
19929
19930 let autoscroll = self
19931 .selections
19932 .all::<Point>(&display_map)
19933 .iter()
19934 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19935
19936 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19937 }
19938
19939 pub fn unfold_all(
19940 &mut self,
19941 _: &actions::UnfoldAll,
19942 _window: &mut Window,
19943 cx: &mut Context<Self>,
19944 ) {
19945 if self.buffer.read(cx).is_singleton() {
19946 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19947 self.unfold_ranges(
19948 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19949 true,
19950 true,
19951 cx,
19952 );
19953 } else {
19954 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19955 editor
19956 .update(cx, |editor, cx| {
19957 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19958 editor.unfold_buffer(buffer_id, cx);
19959 }
19960 })
19961 .ok();
19962 });
19963 }
19964 cx.emit(SearchEvent::ResultsCollapsedChanged(
19965 CollapseDirection::Expanded,
19966 ));
19967 }
19968
19969 pub fn fold_selected_ranges(
19970 &mut self,
19971 _: &FoldSelectedRanges,
19972 window: &mut Window,
19973 cx: &mut Context<Self>,
19974 ) {
19975 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19976 let selections = self.selections.all_adjusted(&display_map);
19977 let ranges = selections
19978 .into_iter()
19979 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19980 .collect::<Vec<_>>();
19981 self.fold_creases(ranges, true, window, cx);
19982 }
19983
19984 pub fn fold_ranges<T: ToOffset + Clone>(
19985 &mut self,
19986 ranges: Vec<Range<T>>,
19987 auto_scroll: bool,
19988 window: &mut Window,
19989 cx: &mut Context<Self>,
19990 ) {
19991 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19992 let ranges = ranges
19993 .into_iter()
19994 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19995 .collect::<Vec<_>>();
19996 self.fold_creases(ranges, auto_scroll, window, cx);
19997 }
19998
19999 pub fn fold_creases<T: ToOffset + Clone>(
20000 &mut self,
20001 creases: Vec<Crease<T>>,
20002 auto_scroll: bool,
20003 _window: &mut Window,
20004 cx: &mut Context<Self>,
20005 ) {
20006 if creases.is_empty() {
20007 return;
20008 }
20009
20010 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20011
20012 if auto_scroll {
20013 self.request_autoscroll(Autoscroll::fit(), cx);
20014 }
20015
20016 cx.notify();
20017
20018 self.scrollbar_marker_state.dirty = true;
20019 self.folds_did_change(cx);
20020 }
20021
20022 /// Removes any folds whose ranges intersect any of the given ranges.
20023 pub fn unfold_ranges<T: ToOffset + Clone>(
20024 &mut self,
20025 ranges: &[Range<T>],
20026 inclusive: bool,
20027 auto_scroll: bool,
20028 cx: &mut Context<Self>,
20029 ) {
20030 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20031 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
20032 });
20033 self.folds_did_change(cx);
20034 }
20035
20036 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20037 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
20038 return;
20039 }
20040
20041 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20042 self.display_map.update(cx, |display_map, cx| {
20043 display_map.fold_buffers([buffer_id], cx)
20044 });
20045
20046 let snapshot = self.display_snapshot(cx);
20047 self.selections.change_with(&snapshot, |selections| {
20048 selections.remove_selections_from_buffer(buffer_id);
20049 });
20050
20051 cx.emit(EditorEvent::BufferFoldToggled {
20052 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
20053 folded: true,
20054 });
20055 cx.notify();
20056 }
20057
20058 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20059 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20060 return;
20061 }
20062 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20063 self.display_map.update(cx, |display_map, cx| {
20064 display_map.unfold_buffers([buffer_id], cx);
20065 });
20066 cx.emit(EditorEvent::BufferFoldToggled {
20067 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20068 folded: false,
20069 });
20070 cx.notify();
20071 }
20072
20073 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20074 self.display_map.read(cx).is_buffer_folded(buffer)
20075 }
20076
20077 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20078 self.display_map.read(cx).folded_buffers()
20079 }
20080
20081 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20082 self.display_map.update(cx, |display_map, cx| {
20083 display_map.disable_header_for_buffer(buffer_id, cx);
20084 });
20085 cx.notify();
20086 }
20087
20088 /// Removes any folds with the given ranges.
20089 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20090 &mut self,
20091 ranges: &[Range<T>],
20092 type_id: TypeId,
20093 auto_scroll: bool,
20094 cx: &mut Context<Self>,
20095 ) {
20096 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20097 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20098 });
20099 self.folds_did_change(cx);
20100 }
20101
20102 fn remove_folds_with<T: ToOffset + Clone>(
20103 &mut self,
20104 ranges: &[Range<T>],
20105 auto_scroll: bool,
20106 cx: &mut Context<Self>,
20107 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20108 ) {
20109 if ranges.is_empty() {
20110 return;
20111 }
20112
20113 let mut buffers_affected = HashSet::default();
20114 let multi_buffer = self.buffer().read(cx);
20115 for range in ranges {
20116 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20117 buffers_affected.insert(buffer.read(cx).remote_id());
20118 };
20119 }
20120
20121 self.display_map.update(cx, update);
20122
20123 if auto_scroll {
20124 self.request_autoscroll(Autoscroll::fit(), cx);
20125 }
20126
20127 cx.notify();
20128 self.scrollbar_marker_state.dirty = true;
20129 self.active_indent_guides_state.dirty = true;
20130 }
20131
20132 pub fn update_renderer_widths(
20133 &mut self,
20134 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20135 cx: &mut Context<Self>,
20136 ) -> bool {
20137 self.display_map
20138 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20139 }
20140
20141 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20142 self.display_map.read(cx).fold_placeholder.clone()
20143 }
20144
20145 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20146 self.buffer.update(cx, |buffer, cx| {
20147 buffer.set_all_diff_hunks_expanded(cx);
20148 });
20149 }
20150
20151 pub fn expand_all_diff_hunks(
20152 &mut self,
20153 _: &ExpandAllDiffHunks,
20154 _window: &mut Window,
20155 cx: &mut Context<Self>,
20156 ) {
20157 self.buffer.update(cx, |buffer, cx| {
20158 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20159 });
20160 }
20161
20162 pub fn collapse_all_diff_hunks(
20163 &mut self,
20164 _: &CollapseAllDiffHunks,
20165 _window: &mut Window,
20166 cx: &mut Context<Self>,
20167 ) {
20168 self.buffer.update(cx, |buffer, cx| {
20169 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20170 });
20171 }
20172
20173 pub fn toggle_selected_diff_hunks(
20174 &mut self,
20175 _: &ToggleSelectedDiffHunks,
20176 _window: &mut Window,
20177 cx: &mut Context<Self>,
20178 ) {
20179 let ranges: Vec<_> = self
20180 .selections
20181 .disjoint_anchors()
20182 .iter()
20183 .map(|s| s.range())
20184 .collect();
20185 self.toggle_diff_hunks_in_ranges(ranges, cx);
20186 }
20187
20188 pub fn diff_hunks_in_ranges<'a>(
20189 &'a self,
20190 ranges: &'a [Range<Anchor>],
20191 buffer: &'a MultiBufferSnapshot,
20192 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20193 ranges.iter().flat_map(move |range| {
20194 let end_excerpt_id = range.end.excerpt_id;
20195 let range = range.to_point(buffer);
20196 let mut peek_end = range.end;
20197 if range.end.row < buffer.max_row().0 {
20198 peek_end = Point::new(range.end.row + 1, 0);
20199 }
20200 buffer
20201 .diff_hunks_in_range(range.start..peek_end)
20202 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20203 })
20204 }
20205
20206 pub fn has_stageable_diff_hunks_in_ranges(
20207 &self,
20208 ranges: &[Range<Anchor>],
20209 snapshot: &MultiBufferSnapshot,
20210 ) -> bool {
20211 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20212 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20213 }
20214
20215 pub fn toggle_staged_selected_diff_hunks(
20216 &mut self,
20217 _: &::git::ToggleStaged,
20218 _: &mut Window,
20219 cx: &mut Context<Self>,
20220 ) {
20221 let snapshot = self.buffer.read(cx).snapshot(cx);
20222 let ranges: Vec<_> = self
20223 .selections
20224 .disjoint_anchors()
20225 .iter()
20226 .map(|s| s.range())
20227 .collect();
20228 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20229 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20230 }
20231
20232 pub fn set_render_diff_hunk_controls(
20233 &mut self,
20234 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20235 cx: &mut Context<Self>,
20236 ) {
20237 self.render_diff_hunk_controls = render_diff_hunk_controls;
20238 cx.notify();
20239 }
20240
20241 pub fn stage_and_next(
20242 &mut self,
20243 _: &::git::StageAndNext,
20244 window: &mut Window,
20245 cx: &mut Context<Self>,
20246 ) {
20247 self.do_stage_or_unstage_and_next(true, window, cx);
20248 }
20249
20250 pub fn unstage_and_next(
20251 &mut self,
20252 _: &::git::UnstageAndNext,
20253 window: &mut Window,
20254 cx: &mut Context<Self>,
20255 ) {
20256 self.do_stage_or_unstage_and_next(false, window, cx);
20257 }
20258
20259 pub fn stage_or_unstage_diff_hunks(
20260 &mut self,
20261 stage: bool,
20262 ranges: Vec<Range<Anchor>>,
20263 cx: &mut Context<Self>,
20264 ) {
20265 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20266 cx.spawn(async move |this, cx| {
20267 task.await?;
20268 this.update(cx, |this, cx| {
20269 let snapshot = this.buffer.read(cx).snapshot(cx);
20270 let chunk_by = this
20271 .diff_hunks_in_ranges(&ranges, &snapshot)
20272 .chunk_by(|hunk| hunk.buffer_id);
20273 for (buffer_id, hunks) in &chunk_by {
20274 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20275 }
20276 })
20277 })
20278 .detach_and_log_err(cx);
20279 }
20280
20281 fn save_buffers_for_ranges_if_needed(
20282 &mut self,
20283 ranges: &[Range<Anchor>],
20284 cx: &mut Context<Editor>,
20285 ) -> Task<Result<()>> {
20286 let multibuffer = self.buffer.read(cx);
20287 let snapshot = multibuffer.read(cx);
20288 let buffer_ids: HashSet<_> = ranges
20289 .iter()
20290 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20291 .collect();
20292 drop(snapshot);
20293
20294 let mut buffers = HashSet::default();
20295 for buffer_id in buffer_ids {
20296 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20297 let buffer = buffer_entity.read(cx);
20298 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20299 {
20300 buffers.insert(buffer_entity);
20301 }
20302 }
20303 }
20304
20305 if let Some(project) = &self.project {
20306 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20307 } else {
20308 Task::ready(Ok(()))
20309 }
20310 }
20311
20312 fn do_stage_or_unstage_and_next(
20313 &mut self,
20314 stage: bool,
20315 window: &mut Window,
20316 cx: &mut Context<Self>,
20317 ) {
20318 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20319
20320 if ranges.iter().any(|range| range.start != range.end) {
20321 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20322 return;
20323 }
20324
20325 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20326 let snapshot = self.snapshot(window, cx);
20327 let position = self
20328 .selections
20329 .newest::<Point>(&snapshot.display_snapshot)
20330 .head();
20331 let mut row = snapshot
20332 .buffer_snapshot()
20333 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20334 .find(|hunk| hunk.row_range.start.0 > position.row)
20335 .map(|hunk| hunk.row_range.start);
20336
20337 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20338 // Outside of the project diff editor, wrap around to the beginning.
20339 if !all_diff_hunks_expanded {
20340 row = row.or_else(|| {
20341 snapshot
20342 .buffer_snapshot()
20343 .diff_hunks_in_range(Point::zero()..position)
20344 .find(|hunk| hunk.row_range.end.0 < position.row)
20345 .map(|hunk| hunk.row_range.start)
20346 });
20347 }
20348
20349 if let Some(row) = row {
20350 let destination = Point::new(row.0, 0);
20351 let autoscroll = Autoscroll::center();
20352
20353 self.unfold_ranges(&[destination..destination], false, false, cx);
20354 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20355 s.select_ranges([destination..destination]);
20356 });
20357 }
20358 }
20359
20360 fn do_stage_or_unstage(
20361 &self,
20362 stage: bool,
20363 buffer_id: BufferId,
20364 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20365 cx: &mut App,
20366 ) -> Option<()> {
20367 let project = self.project()?;
20368 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20369 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20370 let buffer_snapshot = buffer.read(cx).snapshot();
20371 let file_exists = buffer_snapshot
20372 .file()
20373 .is_some_and(|file| file.disk_state().exists());
20374 diff.update(cx, |diff, cx| {
20375 diff.stage_or_unstage_hunks(
20376 stage,
20377 &hunks
20378 .map(|hunk| buffer_diff::DiffHunk {
20379 buffer_range: hunk.buffer_range,
20380 // We don't need to pass in word diffs here because they're only used for rendering and
20381 // this function changes internal state
20382 base_word_diffs: Vec::default(),
20383 buffer_word_diffs: Vec::default(),
20384 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20385 ..hunk.diff_base_byte_range.end.0,
20386 secondary_status: hunk.status.secondary,
20387 range: Point::zero()..Point::zero(), // unused
20388 })
20389 .collect::<Vec<_>>(),
20390 &buffer_snapshot,
20391 file_exists,
20392 cx,
20393 )
20394 });
20395 None
20396 }
20397
20398 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20399 let ranges: Vec<_> = self
20400 .selections
20401 .disjoint_anchors()
20402 .iter()
20403 .map(|s| s.range())
20404 .collect();
20405 self.buffer
20406 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20407 }
20408
20409 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20410 self.buffer.update(cx, |buffer, cx| {
20411 let ranges = vec![Anchor::min()..Anchor::max()];
20412 if !buffer.all_diff_hunks_expanded()
20413 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20414 {
20415 buffer.collapse_diff_hunks(ranges, cx);
20416 true
20417 } else {
20418 false
20419 }
20420 })
20421 }
20422
20423 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20424 if self.buffer.read(cx).all_diff_hunks_expanded() {
20425 return true;
20426 }
20427 let ranges = vec![Anchor::min()..Anchor::max()];
20428 self.buffer
20429 .read(cx)
20430 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20431 }
20432
20433 fn toggle_diff_hunks_in_ranges(
20434 &mut self,
20435 ranges: Vec<Range<Anchor>>,
20436 cx: &mut Context<Editor>,
20437 ) {
20438 self.buffer.update(cx, |buffer, cx| {
20439 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20440 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20441 })
20442 }
20443
20444 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20445 self.buffer.update(cx, |buffer, cx| {
20446 let snapshot = buffer.snapshot(cx);
20447 let excerpt_id = range.end.excerpt_id;
20448 let point_range = range.to_point(&snapshot);
20449 let expand = !buffer.single_hunk_is_expanded(range, cx);
20450 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20451 })
20452 }
20453
20454 pub(crate) fn apply_all_diff_hunks(
20455 &mut self,
20456 _: &ApplyAllDiffHunks,
20457 window: &mut Window,
20458 cx: &mut Context<Self>,
20459 ) {
20460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20461
20462 let buffers = self.buffer.read(cx).all_buffers();
20463 for branch_buffer in buffers {
20464 branch_buffer.update(cx, |branch_buffer, cx| {
20465 branch_buffer.merge_into_base(Vec::new(), cx);
20466 });
20467 }
20468
20469 if let Some(project) = self.project.clone() {
20470 self.save(
20471 SaveOptions {
20472 format: true,
20473 autosave: false,
20474 },
20475 project,
20476 window,
20477 cx,
20478 )
20479 .detach_and_log_err(cx);
20480 }
20481 }
20482
20483 pub(crate) fn apply_selected_diff_hunks(
20484 &mut self,
20485 _: &ApplyDiffHunk,
20486 window: &mut Window,
20487 cx: &mut Context<Self>,
20488 ) {
20489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20490 let snapshot = self.snapshot(window, cx);
20491 let hunks = snapshot.hunks_for_ranges(
20492 self.selections
20493 .all(&snapshot.display_snapshot)
20494 .into_iter()
20495 .map(|selection| selection.range()),
20496 );
20497 let mut ranges_by_buffer = HashMap::default();
20498 self.transact(window, cx, |editor, _window, cx| {
20499 for hunk in hunks {
20500 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20501 ranges_by_buffer
20502 .entry(buffer.clone())
20503 .or_insert_with(Vec::new)
20504 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20505 }
20506 }
20507
20508 for (buffer, ranges) in ranges_by_buffer {
20509 buffer.update(cx, |buffer, cx| {
20510 buffer.merge_into_base(ranges, cx);
20511 });
20512 }
20513 });
20514
20515 if let Some(project) = self.project.clone() {
20516 self.save(
20517 SaveOptions {
20518 format: true,
20519 autosave: false,
20520 },
20521 project,
20522 window,
20523 cx,
20524 )
20525 .detach_and_log_err(cx);
20526 }
20527 }
20528
20529 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20530 if hovered != self.gutter_hovered {
20531 self.gutter_hovered = hovered;
20532 cx.notify();
20533 }
20534 }
20535
20536 pub fn insert_blocks(
20537 &mut self,
20538 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20539 autoscroll: Option<Autoscroll>,
20540 cx: &mut Context<Self>,
20541 ) -> Vec<CustomBlockId> {
20542 let blocks = self
20543 .display_map
20544 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20545 if let Some(autoscroll) = autoscroll {
20546 self.request_autoscroll(autoscroll, cx);
20547 }
20548 cx.notify();
20549 blocks
20550 }
20551
20552 pub fn resize_blocks(
20553 &mut self,
20554 heights: HashMap<CustomBlockId, u32>,
20555 autoscroll: Option<Autoscroll>,
20556 cx: &mut Context<Self>,
20557 ) {
20558 self.display_map
20559 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20560 if let Some(autoscroll) = autoscroll {
20561 self.request_autoscroll(autoscroll, cx);
20562 }
20563 cx.notify();
20564 }
20565
20566 pub fn replace_blocks(
20567 &mut self,
20568 renderers: HashMap<CustomBlockId, RenderBlock>,
20569 autoscroll: Option<Autoscroll>,
20570 cx: &mut Context<Self>,
20571 ) {
20572 self.display_map
20573 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20574 if let Some(autoscroll) = autoscroll {
20575 self.request_autoscroll(autoscroll, cx);
20576 }
20577 cx.notify();
20578 }
20579
20580 pub fn remove_blocks(
20581 &mut self,
20582 block_ids: HashSet<CustomBlockId>,
20583 autoscroll: Option<Autoscroll>,
20584 cx: &mut Context<Self>,
20585 ) {
20586 self.display_map.update(cx, |display_map, cx| {
20587 display_map.remove_blocks(block_ids, cx)
20588 });
20589 if let Some(autoscroll) = autoscroll {
20590 self.request_autoscroll(autoscroll, cx);
20591 }
20592 cx.notify();
20593 }
20594
20595 pub fn row_for_block(
20596 &self,
20597 block_id: CustomBlockId,
20598 cx: &mut Context<Self>,
20599 ) -> Option<DisplayRow> {
20600 self.display_map
20601 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20602 }
20603
20604 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20605 self.focused_block = Some(focused_block);
20606 }
20607
20608 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20609 self.focused_block.take()
20610 }
20611
20612 pub fn insert_creases(
20613 &mut self,
20614 creases: impl IntoIterator<Item = Crease<Anchor>>,
20615 cx: &mut Context<Self>,
20616 ) -> Vec<CreaseId> {
20617 self.display_map
20618 .update(cx, |map, cx| map.insert_creases(creases, cx))
20619 }
20620
20621 pub fn remove_creases(
20622 &mut self,
20623 ids: impl IntoIterator<Item = CreaseId>,
20624 cx: &mut Context<Self>,
20625 ) -> Vec<(CreaseId, Range<Anchor>)> {
20626 self.display_map
20627 .update(cx, |map, cx| map.remove_creases(ids, cx))
20628 }
20629
20630 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20631 self.display_map
20632 .update(cx, |map, cx| map.snapshot(cx))
20633 .longest_row()
20634 }
20635
20636 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20637 self.display_map
20638 .update(cx, |map, cx| map.snapshot(cx))
20639 .max_point()
20640 }
20641
20642 pub fn text(&self, cx: &App) -> String {
20643 self.buffer.read(cx).read(cx).text()
20644 }
20645
20646 pub fn is_empty(&self, cx: &App) -> bool {
20647 self.buffer.read(cx).read(cx).is_empty()
20648 }
20649
20650 pub fn text_option(&self, cx: &App) -> Option<String> {
20651 let text = self.text(cx);
20652 let text = text.trim();
20653
20654 if text.is_empty() {
20655 return None;
20656 }
20657
20658 Some(text.to_string())
20659 }
20660
20661 pub fn set_text(
20662 &mut self,
20663 text: impl Into<Arc<str>>,
20664 window: &mut Window,
20665 cx: &mut Context<Self>,
20666 ) {
20667 self.transact(window, cx, |this, _, cx| {
20668 this.buffer
20669 .read(cx)
20670 .as_singleton()
20671 .expect("you can only call set_text on editors for singleton buffers")
20672 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20673 });
20674 }
20675
20676 pub fn display_text(&self, cx: &mut App) -> String {
20677 self.display_map
20678 .update(cx, |map, cx| map.snapshot(cx))
20679 .text()
20680 }
20681
20682 fn create_minimap(
20683 &self,
20684 minimap_settings: MinimapSettings,
20685 window: &mut Window,
20686 cx: &mut Context<Self>,
20687 ) -> Option<Entity<Self>> {
20688 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20689 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20690 }
20691
20692 fn initialize_new_minimap(
20693 &self,
20694 minimap_settings: MinimapSettings,
20695 window: &mut Window,
20696 cx: &mut Context<Self>,
20697 ) -> Entity<Self> {
20698 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20699 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20700
20701 let mut minimap = Editor::new_internal(
20702 EditorMode::Minimap {
20703 parent: cx.weak_entity(),
20704 },
20705 self.buffer.clone(),
20706 None,
20707 Some(self.display_map.clone()),
20708 window,
20709 cx,
20710 );
20711 minimap.scroll_manager.clone_state(&self.scroll_manager);
20712 minimap.set_text_style_refinement(TextStyleRefinement {
20713 font_size: Some(MINIMAP_FONT_SIZE),
20714 font_weight: Some(MINIMAP_FONT_WEIGHT),
20715 font_family: Some(MINIMAP_FONT_FAMILY),
20716 ..Default::default()
20717 });
20718 minimap.update_minimap_configuration(minimap_settings, cx);
20719 cx.new(|_| minimap)
20720 }
20721
20722 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20723 let current_line_highlight = minimap_settings
20724 .current_line_highlight
20725 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20726 self.set_current_line_highlight(Some(current_line_highlight));
20727 }
20728
20729 pub fn minimap(&self) -> Option<&Entity<Self>> {
20730 self.minimap
20731 .as_ref()
20732 .filter(|_| self.minimap_visibility.visible())
20733 }
20734
20735 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20736 let mut wrap_guides = smallvec![];
20737
20738 if self.show_wrap_guides == Some(false) {
20739 return wrap_guides;
20740 }
20741
20742 let settings = self.buffer.read(cx).language_settings(cx);
20743 if settings.show_wrap_guides {
20744 match self.soft_wrap_mode(cx) {
20745 SoftWrap::Column(soft_wrap) => {
20746 wrap_guides.push((soft_wrap as usize, true));
20747 }
20748 SoftWrap::Bounded(soft_wrap) => {
20749 wrap_guides.push((soft_wrap as usize, true));
20750 }
20751 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20752 }
20753 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20754 }
20755
20756 wrap_guides
20757 }
20758
20759 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20760 let settings = self.buffer.read(cx).language_settings(cx);
20761 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20762 match mode {
20763 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20764 SoftWrap::None
20765 }
20766 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20767 language_settings::SoftWrap::PreferredLineLength => {
20768 SoftWrap::Column(settings.preferred_line_length)
20769 }
20770 language_settings::SoftWrap::Bounded => {
20771 SoftWrap::Bounded(settings.preferred_line_length)
20772 }
20773 }
20774 }
20775
20776 pub fn set_soft_wrap_mode(
20777 &mut self,
20778 mode: language_settings::SoftWrap,
20779
20780 cx: &mut Context<Self>,
20781 ) {
20782 self.soft_wrap_mode_override = Some(mode);
20783 cx.notify();
20784 }
20785
20786 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20787 self.hard_wrap = hard_wrap;
20788 cx.notify();
20789 }
20790
20791 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20792 self.text_style_refinement = Some(style);
20793 }
20794
20795 /// called by the Element so we know what style we were most recently rendered with.
20796 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20797 // We intentionally do not inform the display map about the minimap style
20798 // so that wrapping is not recalculated and stays consistent for the editor
20799 // and its linked minimap.
20800 if !self.mode.is_minimap() {
20801 let font = style.text.font();
20802 let font_size = style.text.font_size.to_pixels(window.rem_size());
20803 let display_map = self
20804 .placeholder_display_map
20805 .as_ref()
20806 .filter(|_| self.is_empty(cx))
20807 .unwrap_or(&self.display_map);
20808
20809 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20810 }
20811 self.style = Some(style);
20812 }
20813
20814 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20815 if self.style.is_none() {
20816 self.style = Some(self.create_style(cx));
20817 }
20818 self.style.as_ref().unwrap()
20819 }
20820
20821 // Called by the element. This method is not designed to be called outside of the editor
20822 // element's layout code because it does not notify when rewrapping is computed synchronously.
20823 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20824 if self.is_empty(cx) {
20825 self.placeholder_display_map
20826 .as_ref()
20827 .map_or(false, |display_map| {
20828 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20829 })
20830 } else {
20831 self.display_map
20832 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20833 }
20834 }
20835
20836 pub fn set_soft_wrap(&mut self) {
20837 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20838 }
20839
20840 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20841 if self.soft_wrap_mode_override.is_some() {
20842 self.soft_wrap_mode_override.take();
20843 } else {
20844 let soft_wrap = match self.soft_wrap_mode(cx) {
20845 SoftWrap::GitDiff => return,
20846 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20847 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20848 language_settings::SoftWrap::None
20849 }
20850 };
20851 self.soft_wrap_mode_override = Some(soft_wrap);
20852 }
20853 cx.notify();
20854 }
20855
20856 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20857 let Some(workspace) = self.workspace() else {
20858 return;
20859 };
20860 let fs = workspace.read(cx).app_state().fs.clone();
20861 let current_show = TabBarSettings::get_global(cx).show;
20862 update_settings_file(fs, cx, move |setting, _| {
20863 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20864 });
20865 }
20866
20867 pub fn toggle_indent_guides(
20868 &mut self,
20869 _: &ToggleIndentGuides,
20870 _: &mut Window,
20871 cx: &mut Context<Self>,
20872 ) {
20873 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20874 self.buffer
20875 .read(cx)
20876 .language_settings(cx)
20877 .indent_guides
20878 .enabled
20879 });
20880 self.show_indent_guides = Some(!currently_enabled);
20881 cx.notify();
20882 }
20883
20884 fn should_show_indent_guides(&self) -> Option<bool> {
20885 self.show_indent_guides
20886 }
20887
20888 pub fn disable_indent_guides_for_buffer(
20889 &mut self,
20890 buffer_id: BufferId,
20891 cx: &mut Context<Self>,
20892 ) {
20893 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20894 cx.notify();
20895 }
20896
20897 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20898 self.buffers_with_disabled_indent_guides
20899 .contains(&buffer_id)
20900 }
20901
20902 pub fn toggle_line_numbers(
20903 &mut self,
20904 _: &ToggleLineNumbers,
20905 _: &mut Window,
20906 cx: &mut Context<Self>,
20907 ) {
20908 let mut editor_settings = EditorSettings::get_global(cx).clone();
20909 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20910 EditorSettings::override_global(editor_settings, cx);
20911 }
20912
20913 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20914 if let Some(show_line_numbers) = self.show_line_numbers {
20915 return show_line_numbers;
20916 }
20917 EditorSettings::get_global(cx).gutter.line_numbers
20918 }
20919
20920 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20921 match (
20922 self.use_relative_line_numbers,
20923 EditorSettings::get_global(cx).relative_line_numbers,
20924 ) {
20925 (None, setting) => setting,
20926 (Some(false), _) => RelativeLineNumbers::Disabled,
20927 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20928 (Some(true), _) => RelativeLineNumbers::Enabled,
20929 }
20930 }
20931
20932 pub fn toggle_relative_line_numbers(
20933 &mut self,
20934 _: &ToggleRelativeLineNumbers,
20935 _: &mut Window,
20936 cx: &mut Context<Self>,
20937 ) {
20938 let is_relative = self.relative_line_numbers(cx);
20939 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20940 }
20941
20942 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20943 self.use_relative_line_numbers = is_relative;
20944 cx.notify();
20945 }
20946
20947 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20948 self.show_gutter = show_gutter;
20949 cx.notify();
20950 }
20951
20952 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20953 self.show_scrollbars = ScrollbarAxes {
20954 horizontal: show,
20955 vertical: show,
20956 };
20957 cx.notify();
20958 }
20959
20960 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20961 self.show_scrollbars.vertical = show;
20962 cx.notify();
20963 }
20964
20965 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20966 self.show_scrollbars.horizontal = show;
20967 cx.notify();
20968 }
20969
20970 pub fn set_minimap_visibility(
20971 &mut self,
20972 minimap_visibility: MinimapVisibility,
20973 window: &mut Window,
20974 cx: &mut Context<Self>,
20975 ) {
20976 if self.minimap_visibility != minimap_visibility {
20977 if minimap_visibility.visible() && self.minimap.is_none() {
20978 let minimap_settings = EditorSettings::get_global(cx).minimap;
20979 self.minimap =
20980 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20981 }
20982 self.minimap_visibility = minimap_visibility;
20983 cx.notify();
20984 }
20985 }
20986
20987 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20988 self.set_show_scrollbars(false, cx);
20989 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20990 }
20991
20992 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20993 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20994 }
20995
20996 /// Normally the text in full mode and auto height editors is padded on the
20997 /// left side by roughly half a character width for improved hit testing.
20998 ///
20999 /// Use this method to disable this for cases where this is not wanted (e.g.
21000 /// if you want to align the editor text with some other text above or below)
21001 /// or if you want to add this padding to single-line editors.
21002 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21003 self.offset_content = offset_content;
21004 cx.notify();
21005 }
21006
21007 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21008 self.show_line_numbers = Some(show_line_numbers);
21009 cx.notify();
21010 }
21011
21012 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21013 self.disable_expand_excerpt_buttons = true;
21014 cx.notify();
21015 }
21016
21017 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21018 self.delegate_expand_excerpts = delegate;
21019 }
21020
21021 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21022 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21023 cx.notify();
21024 }
21025
21026 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21027 self.show_code_actions = Some(show_code_actions);
21028 cx.notify();
21029 }
21030
21031 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21032 self.show_runnables = Some(show_runnables);
21033 cx.notify();
21034 }
21035
21036 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21037 self.show_breakpoints = Some(show_breakpoints);
21038 cx.notify();
21039 }
21040
21041 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21042 self.show_diff_review_button = show;
21043 cx.notify();
21044 }
21045
21046 pub fn show_diff_review_button(&self) -> bool {
21047 self.show_diff_review_button
21048 }
21049
21050 pub fn render_diff_review_button(
21051 &self,
21052 display_row: DisplayRow,
21053 width: Pixels,
21054 cx: &mut Context<Self>,
21055 ) -> impl IntoElement {
21056 let text_color = cx.theme().colors().text;
21057 let icon_color = cx.theme().colors().icon_accent;
21058
21059 h_flex()
21060 .id("diff_review_button")
21061 .cursor_pointer()
21062 .w(width - px(1.))
21063 .h(relative(0.9))
21064 .justify_center()
21065 .rounded_sm()
21066 .border_1()
21067 .border_color(text_color.opacity(0.1))
21068 .bg(text_color.opacity(0.15))
21069 .hover(|s| {
21070 s.bg(icon_color.opacity(0.4))
21071 .border_color(icon_color.opacity(0.5))
21072 })
21073 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21074 .tooltip(Tooltip::text("Add Review"))
21075 .on_click(cx.listener(move |editor, _event: &ClickEvent, window, cx| {
21076 editor.show_diff_review_overlay(display_row, window, cx);
21077 }))
21078 }
21079
21080 /// Calculates the appropriate block height for the diff review overlay.
21081 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21082 /// and 2 lines per comment when expanded.
21083 fn calculate_overlay_height(
21084 &self,
21085 hunk_key: &DiffHunkKey,
21086 comments_expanded: bool,
21087 snapshot: &MultiBufferSnapshot,
21088 ) -> u32 {
21089 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21090 let base_height: u32 = 2; // Input row with avatar and buttons
21091
21092 if comment_count == 0 {
21093 base_height
21094 } else if comments_expanded {
21095 // Header (1 line) + 2 lines per comment
21096 base_height + 1 + (comment_count as u32 * 2)
21097 } else {
21098 // Just header when collapsed
21099 base_height + 1
21100 }
21101 }
21102
21103 pub fn show_diff_review_overlay(
21104 &mut self,
21105 display_row: DisplayRow,
21106 window: &mut Window,
21107 cx: &mut Context<Self>,
21108 ) {
21109 // Check if there's already an overlay for the same hunk - if so, just focus it
21110 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21111 let editor_snapshot = self.snapshot(window, cx);
21112 let display_point = DisplayPoint::new(display_row, 0);
21113 let buffer_point = editor_snapshot
21114 .display_snapshot
21115 .display_point_to_point(display_point, Bias::Left);
21116
21117 // Compute the hunk key for this display row
21118 let file_path = buffer_snapshot
21119 .file_at(Point::new(buffer_point.row, 0))
21120 .map(|file: &Arc<dyn language::File>| file.path().clone())
21121 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21122 let hunk_start_anchor = buffer_snapshot.anchor_before(Point::new(buffer_point.row, 0));
21123 let new_hunk_key = DiffHunkKey {
21124 file_path,
21125 hunk_start_anchor,
21126 };
21127
21128 // Check if we already have an overlay for this hunk
21129 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21130 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21131 }) {
21132 // Just focus the existing overlay's prompt editor
21133 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21134 window.focus(&focus_handle, cx);
21135 return;
21136 }
21137
21138 // Dismiss overlays that have no comments for their hunks
21139 self.dismiss_overlays_without_comments(cx);
21140
21141 // Get the current user's avatar URI from the project's user_store
21142 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21143 let user_store = project.read(cx).user_store();
21144 user_store
21145 .read(cx)
21146 .current_user()
21147 .map(|user| user.avatar_uri.clone())
21148 });
21149
21150 // Create anchor at the end of the row so the block appears immediately below it
21151 let line_len = buffer_snapshot.line_len(MultiBufferRow(buffer_point.row));
21152 let anchor = buffer_snapshot.anchor_after(Point::new(buffer_point.row, line_len));
21153
21154 // Use the hunk key we already computed
21155 let hunk_key = new_hunk_key;
21156
21157 // Create the prompt editor for the review input
21158 let prompt_editor = cx.new(|cx| {
21159 let mut editor = Editor::single_line(window, cx);
21160 editor.set_placeholder_text("Add a review comment...", window, cx);
21161 editor
21162 });
21163
21164 // Register the Newline action on the prompt editor to submit the review
21165 let parent_editor = cx.entity().downgrade();
21166 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21167 prompt_editor.register_action({
21168 let parent_editor = parent_editor.clone();
21169 move |_: &crate::actions::Newline, window, cx| {
21170 if let Some(editor) = parent_editor.upgrade() {
21171 editor.update(cx, |editor, cx| {
21172 editor.submit_diff_review_comment(window, cx);
21173 });
21174 }
21175 }
21176 })
21177 });
21178
21179 // Calculate initial height based on existing comments for this hunk
21180 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21181
21182 // Create the overlay block
21183 let prompt_editor_for_render = prompt_editor.clone();
21184 let hunk_key_for_render = hunk_key.clone();
21185 let editor_handle = cx.entity().downgrade();
21186 let block = BlockProperties {
21187 style: BlockStyle::Sticky,
21188 placement: BlockPlacement::Below(anchor),
21189 height: Some(initial_height),
21190 render: Arc::new(move |cx| {
21191 Self::render_diff_review_overlay(
21192 &prompt_editor_for_render,
21193 &hunk_key_for_render,
21194 &editor_handle,
21195 cx,
21196 )
21197 }),
21198 priority: 0,
21199 };
21200
21201 let block_ids = self.insert_blocks([block], None, cx);
21202 let Some(block_id) = block_ids.into_iter().next() else {
21203 log::error!("Failed to insert diff review overlay block");
21204 return;
21205 };
21206
21207 self.diff_review_overlays.push(DiffReviewOverlay {
21208 display_row,
21209 block_id,
21210 prompt_editor: prompt_editor.clone(),
21211 hunk_key,
21212 comments_expanded: true,
21213 inline_edit_editors: HashMap::default(),
21214 inline_edit_subscriptions: HashMap::default(),
21215 user_avatar_uri,
21216 _subscription: subscription,
21217 });
21218
21219 // Focus the prompt editor
21220 let focus_handle = prompt_editor.focus_handle(cx);
21221 window.focus(&focus_handle, cx);
21222
21223 cx.notify();
21224 }
21225
21226 /// Dismisses all diff review overlays.
21227 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21228 if self.diff_review_overlays.is_empty() {
21229 return;
21230 }
21231 let block_ids: HashSet<_> = self
21232 .diff_review_overlays
21233 .drain(..)
21234 .map(|overlay| overlay.block_id)
21235 .collect();
21236 self.remove_blocks(block_ids, None, cx);
21237 cx.notify();
21238 }
21239
21240 /// Dismisses overlays that have no comments stored for their hunks.
21241 /// Keeps overlays that have at least one comment.
21242 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21243 let snapshot = self.buffer.read(cx).snapshot(cx);
21244
21245 // First, compute which overlays have comments (to avoid borrow issues with retain)
21246 let overlays_with_comments: Vec<bool> = self
21247 .diff_review_overlays
21248 .iter()
21249 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21250 .collect();
21251
21252 // Now collect block IDs to remove and retain overlays
21253 let mut block_ids_to_remove = HashSet::default();
21254 let mut index = 0;
21255 self.diff_review_overlays.retain(|overlay| {
21256 let has_comments = overlays_with_comments[index];
21257 index += 1;
21258 if !has_comments {
21259 block_ids_to_remove.insert(overlay.block_id);
21260 }
21261 has_comments
21262 });
21263
21264 if !block_ids_to_remove.is_empty() {
21265 self.remove_blocks(block_ids_to_remove, None, cx);
21266 cx.notify();
21267 }
21268 }
21269
21270 /// Refreshes the diff review overlay block to update its height and render function.
21271 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21272 fn refresh_diff_review_overlay_height(
21273 &mut self,
21274 hunk_key: &DiffHunkKey,
21275 _window: &mut Window,
21276 cx: &mut Context<Self>,
21277 ) {
21278 // Extract all needed data from overlay first to avoid borrow conflicts
21279 let snapshot = self.buffer.read(cx).snapshot(cx);
21280 let (comments_expanded, block_id, prompt_editor) = {
21281 let Some(overlay) = self
21282 .diff_review_overlays
21283 .iter()
21284 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21285 else {
21286 return;
21287 };
21288
21289 (
21290 overlay.comments_expanded,
21291 overlay.block_id,
21292 overlay.prompt_editor.clone(),
21293 )
21294 };
21295
21296 // Calculate new height
21297 let snapshot = self.buffer.read(cx).snapshot(cx);
21298 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21299
21300 // Update the block height using resize_blocks (avoids flicker)
21301 let mut heights = HashMap::default();
21302 heights.insert(block_id, new_height);
21303 self.resize_blocks(heights, None, cx);
21304
21305 // Update the render function using replace_blocks (avoids flicker)
21306 let hunk_key_for_render = hunk_key.clone();
21307 let editor_handle = cx.entity().downgrade();
21308 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21309 Arc::new(move |cx| {
21310 Self::render_diff_review_overlay(
21311 &prompt_editor,
21312 &hunk_key_for_render,
21313 &editor_handle,
21314 cx,
21315 )
21316 });
21317
21318 let mut renderers = HashMap::default();
21319 renderers.insert(block_id, render);
21320 self.replace_blocks(renderers, None, cx);
21321 }
21322
21323 /// Action handler for SubmitDiffReviewComment.
21324 pub fn submit_diff_review_comment_action(
21325 &mut self,
21326 _: &SubmitDiffReviewComment,
21327 window: &mut Window,
21328 cx: &mut Context<Self>,
21329 ) {
21330 self.submit_diff_review_comment(window, cx);
21331 }
21332
21333 /// Stores the diff review comment locally.
21334 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21335 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21336 // Find the overlay that currently has focus
21337 let overlay_index = self
21338 .diff_review_overlays
21339 .iter()
21340 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21341 let Some(overlay_index) = overlay_index else {
21342 return;
21343 };
21344 let overlay = &self.diff_review_overlays[overlay_index];
21345
21346 // Get the comment text from the prompt editor
21347 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21348
21349 // Don't submit if the comment is empty
21350 if comment_text.is_empty() {
21351 return;
21352 }
21353
21354 // Get the display row and hunk key
21355 let display_row = overlay.display_row;
21356 let hunk_key = overlay.hunk_key.clone();
21357
21358 // Convert to buffer position for anchors
21359 let snapshot = self.snapshot(window, cx);
21360 let display_point = DisplayPoint::new(display_row, 0);
21361 let buffer_point = snapshot
21362 .display_snapshot
21363 .display_point_to_point(display_point, Bias::Left);
21364
21365 // Get the line range
21366 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21367 let line_start = Point::new(buffer_point.row, 0);
21368 let line_end = Point::new(
21369 buffer_point.row,
21370 buffer_snapshot.line_len(MultiBufferRow(buffer_point.row)),
21371 );
21372
21373 // Create anchors for the selection
21374 let anchor_start = buffer_snapshot.anchor_after(line_start);
21375 let anchor_end = buffer_snapshot.anchor_before(line_end);
21376
21377 // Store the comment locally
21378 self.add_review_comment(
21379 hunk_key.clone(),
21380 comment_text,
21381 display_row,
21382 anchor_start..anchor_end,
21383 cx,
21384 );
21385
21386 // Clear the prompt editor but keep the overlay open
21387 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21388 overlay.prompt_editor.update(cx, |editor, cx| {
21389 editor.clear(window, cx);
21390 });
21391 }
21392
21393 // Refresh the overlay to update the block height for the new comment
21394 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21395
21396 cx.notify();
21397 }
21398
21399 /// Returns the prompt editor for the diff review overlay, if one is active.
21400 /// This is primarily used for testing.
21401 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21402 self.diff_review_overlays
21403 .first()
21404 .map(|overlay| &overlay.prompt_editor)
21405 }
21406
21407 /// Returns the display row for the first diff review overlay, if one is active.
21408 pub fn diff_review_display_row(&self) -> Option<DisplayRow> {
21409 self.diff_review_overlays
21410 .first()
21411 .map(|overlay| overlay.display_row)
21412 }
21413
21414 /// Sets whether the comments section is expanded in the diff review overlay.
21415 /// This is primarily used for testing.
21416 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21417 for overlay in &mut self.diff_review_overlays {
21418 overlay.comments_expanded = expanded;
21419 }
21420 cx.notify();
21421 }
21422
21423 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21424 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21425 a.file_path == b.file_path
21426 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21427 }
21428
21429 /// Returns comments for a specific hunk, ordered by creation time.
21430 pub fn comments_for_hunk<'a>(
21431 &'a self,
21432 key: &DiffHunkKey,
21433 snapshot: &MultiBufferSnapshot,
21434 ) -> &'a [StoredReviewComment] {
21435 let key_point = key.hunk_start_anchor.to_point(snapshot);
21436 self.stored_review_comments
21437 .iter()
21438 .find(|(k, _)| {
21439 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21440 })
21441 .map(|(_, comments)| comments.as_slice())
21442 .unwrap_or(&[])
21443 }
21444
21445 /// Returns the total count of stored review comments across all hunks.
21446 pub fn total_review_comment_count(&self) -> usize {
21447 self.stored_review_comments
21448 .iter()
21449 .map(|(_, v)| v.len())
21450 .sum()
21451 }
21452
21453 /// Returns the count of comments for a specific hunk.
21454 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21455 let key_point = key.hunk_start_anchor.to_point(snapshot);
21456 self.stored_review_comments
21457 .iter()
21458 .find(|(k, _)| {
21459 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21460 })
21461 .map(|(_, v)| v.len())
21462 .unwrap_or(0)
21463 }
21464
21465 /// Adds a new review comment to a specific hunk.
21466 pub fn add_review_comment(
21467 &mut self,
21468 hunk_key: DiffHunkKey,
21469 comment: String,
21470 display_row: DisplayRow,
21471 anchor_range: Range<Anchor>,
21472 cx: &mut Context<Self>,
21473 ) -> usize {
21474 let id = self.next_review_comment_id;
21475 self.next_review_comment_id += 1;
21476
21477 let stored_comment = StoredReviewComment::new(id, comment, display_row, anchor_range);
21478
21479 let snapshot = self.buffer.read(cx).snapshot(cx);
21480 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21481
21482 // Find existing entry for this hunk or add a new one
21483 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21484 k.file_path == hunk_key.file_path
21485 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21486 }) {
21487 comments.push(stored_comment);
21488 } else {
21489 self.stored_review_comments
21490 .push((hunk_key, vec![stored_comment]));
21491 }
21492
21493 cx.emit(EditorEvent::ReviewCommentsChanged {
21494 total_count: self.total_review_comment_count(),
21495 });
21496 cx.notify();
21497 id
21498 }
21499
21500 /// Removes a review comment by ID from any hunk.
21501 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21502 for (_, comments) in self.stored_review_comments.iter_mut() {
21503 if let Some(index) = comments.iter().position(|c| c.id == id) {
21504 comments.remove(index);
21505 cx.emit(EditorEvent::ReviewCommentsChanged {
21506 total_count: self.total_review_comment_count(),
21507 });
21508 cx.notify();
21509 return true;
21510 }
21511 }
21512 false
21513 }
21514
21515 /// Updates a review comment's text by ID.
21516 pub fn update_review_comment(
21517 &mut self,
21518 id: usize,
21519 new_comment: String,
21520 cx: &mut Context<Self>,
21521 ) -> bool {
21522 for (_, comments) in self.stored_review_comments.iter_mut() {
21523 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21524 comment.comment = new_comment;
21525 comment.is_editing = false;
21526 cx.emit(EditorEvent::ReviewCommentsChanged {
21527 total_count: self.total_review_comment_count(),
21528 });
21529 cx.notify();
21530 return true;
21531 }
21532 }
21533 false
21534 }
21535
21536 /// Sets a comment's editing state.
21537 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21538 for (_, comments) in self.stored_review_comments.iter_mut() {
21539 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21540 comment.is_editing = is_editing;
21541 cx.notify();
21542 return;
21543 }
21544 }
21545 }
21546
21547 /// Takes all stored comments from all hunks, clearing the storage.
21548 /// Returns a Vec of (hunk_key, comments) pairs.
21549 pub fn take_all_review_comments(
21550 &mut self,
21551 cx: &mut Context<Self>,
21552 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21553 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21554 self.dismiss_all_diff_review_overlays(cx);
21555 let comments = std::mem::take(&mut self.stored_review_comments);
21556 // Reset the ID counter since all comments have been taken
21557 self.next_review_comment_id = 0;
21558 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21559 cx.notify();
21560 comments
21561 }
21562
21563 /// Removes review comments whose anchors are no longer valid or whose
21564 /// associated diff hunks no longer exist.
21565 ///
21566 /// This should be called when the buffer changes to prevent orphaned comments
21567 /// from accumulating.
21568 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21569 let snapshot = self.buffer.read(cx).snapshot(cx);
21570 let original_count = self.total_review_comment_count();
21571
21572 // Remove comments with invalid hunk anchors
21573 self.stored_review_comments
21574 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21575
21576 // Also clean up individual comments with invalid anchor ranges
21577 for (_, comments) in &mut self.stored_review_comments {
21578 comments.retain(|comment| {
21579 comment.anchor_range.start.is_valid(&snapshot)
21580 && comment.anchor_range.end.is_valid(&snapshot)
21581 });
21582 }
21583
21584 // Remove empty hunk entries
21585 self.stored_review_comments
21586 .retain(|(_, comments)| !comments.is_empty());
21587
21588 let new_count = self.total_review_comment_count();
21589 if new_count != original_count {
21590 cx.emit(EditorEvent::ReviewCommentsChanged {
21591 total_count: new_count,
21592 });
21593 cx.notify();
21594 }
21595 }
21596
21597 /// Toggles the expanded state of the comments section in the overlay.
21598 pub fn toggle_review_comments_expanded(
21599 &mut self,
21600 _: &ToggleReviewCommentsExpanded,
21601 window: &mut Window,
21602 cx: &mut Context<Self>,
21603 ) {
21604 // Find the overlay that currently has focus, or use the first one
21605 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21606 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21607 overlay.comments_expanded = !overlay.comments_expanded;
21608 Some(overlay.hunk_key.clone())
21609 } else {
21610 None
21611 }
21612 });
21613
21614 // If no focused overlay found, toggle the first one
21615 let hunk_key = overlay_info.or_else(|| {
21616 self.diff_review_overlays.first_mut().map(|overlay| {
21617 overlay.comments_expanded = !overlay.comments_expanded;
21618 overlay.hunk_key.clone()
21619 })
21620 });
21621
21622 if let Some(hunk_key) = hunk_key {
21623 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21624 cx.notify();
21625 }
21626 }
21627
21628 /// Handles the EditReviewComment action - sets a comment into editing mode.
21629 pub fn edit_review_comment(
21630 &mut self,
21631 action: &EditReviewComment,
21632 window: &mut Window,
21633 cx: &mut Context<Self>,
21634 ) {
21635 let comment_id = action.id;
21636
21637 // Set the comment to editing mode
21638 self.set_comment_editing(comment_id, true, cx);
21639
21640 // Find the overlay that contains this comment and create an inline editor if needed
21641 // First, find which hunk this comment belongs to
21642 let hunk_key = self
21643 .stored_review_comments
21644 .iter()
21645 .find_map(|(key, comments)| {
21646 if comments.iter().any(|c| c.id == comment_id) {
21647 Some(key.clone())
21648 } else {
21649 None
21650 }
21651 });
21652
21653 let snapshot = self.buffer.read(cx).snapshot(cx);
21654 if let Some(hunk_key) = hunk_key {
21655 if let Some(overlay) = self
21656 .diff_review_overlays
21657 .iter_mut()
21658 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21659 {
21660 if let std::collections::hash_map::Entry::Vacant(entry) =
21661 overlay.inline_edit_editors.entry(comment_id)
21662 {
21663 // Find the comment text
21664 let comment_text = self
21665 .stored_review_comments
21666 .iter()
21667 .flat_map(|(_, comments)| comments)
21668 .find(|c| c.id == comment_id)
21669 .map(|c| c.comment.clone())
21670 .unwrap_or_default();
21671
21672 // Create inline editor
21673 let parent_editor = cx.entity().downgrade();
21674 let inline_editor = cx.new(|cx| {
21675 let mut editor = Editor::single_line(window, cx);
21676 editor.set_text(&*comment_text, window, cx);
21677 // Select all text for easy replacement
21678 editor.select_all(&crate::actions::SelectAll, window, cx);
21679 editor
21680 });
21681
21682 // Register the Newline action to confirm the edit
21683 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
21684 inline_editor.register_action({
21685 let parent_editor = parent_editor.clone();
21686 move |_: &crate::actions::Newline, window, cx| {
21687 if let Some(editor) = parent_editor.upgrade() {
21688 editor.update(cx, |editor, cx| {
21689 editor.confirm_edit_review_comment(comment_id, window, cx);
21690 });
21691 }
21692 }
21693 })
21694 });
21695
21696 // Store the subscription to keep the action handler alive
21697 overlay
21698 .inline_edit_subscriptions
21699 .insert(comment_id, subscription);
21700
21701 // Focus the inline editor
21702 let focus_handle = inline_editor.focus_handle(cx);
21703 window.focus(&focus_handle, cx);
21704
21705 entry.insert(inline_editor);
21706 }
21707 }
21708 }
21709
21710 cx.notify();
21711 }
21712
21713 /// Confirms an inline edit of a review comment.
21714 pub fn confirm_edit_review_comment(
21715 &mut self,
21716 comment_id: usize,
21717 _window: &mut Window,
21718 cx: &mut Context<Self>,
21719 ) {
21720 // Get the new text from the inline editor
21721 // Find the overlay containing this comment's inline editor
21722 let snapshot = self.buffer.read(cx).snapshot(cx);
21723 let hunk_key = self
21724 .stored_review_comments
21725 .iter()
21726 .find_map(|(key, comments)| {
21727 if comments.iter().any(|c| c.id == comment_id) {
21728 Some(key.clone())
21729 } else {
21730 None
21731 }
21732 });
21733
21734 let new_text = hunk_key
21735 .as_ref()
21736 .and_then(|hunk_key| {
21737 self.diff_review_overlays
21738 .iter()
21739 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21740 })
21741 .as_ref()
21742 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
21743 .map(|editor| editor.read(cx).text(cx).trim().to_string());
21744
21745 if let Some(new_text) = new_text {
21746 if !new_text.is_empty() {
21747 self.update_review_comment(comment_id, new_text, cx);
21748 }
21749 }
21750
21751 // Remove the inline editor and its subscription
21752 if let Some(hunk_key) = hunk_key {
21753 if let Some(overlay) = self
21754 .diff_review_overlays
21755 .iter_mut()
21756 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21757 {
21758 overlay.inline_edit_editors.remove(&comment_id);
21759 overlay.inline_edit_subscriptions.remove(&comment_id);
21760 }
21761 }
21762
21763 // Clear editing state
21764 self.set_comment_editing(comment_id, false, cx);
21765 }
21766
21767 /// Cancels an inline edit of a review comment.
21768 pub fn cancel_edit_review_comment(
21769 &mut self,
21770 comment_id: usize,
21771 _window: &mut Window,
21772 cx: &mut Context<Self>,
21773 ) {
21774 // Find which hunk this comment belongs to
21775 let hunk_key = self
21776 .stored_review_comments
21777 .iter()
21778 .find_map(|(key, comments)| {
21779 if comments.iter().any(|c| c.id == comment_id) {
21780 Some(key.clone())
21781 } else {
21782 None
21783 }
21784 });
21785
21786 // Remove the inline editor and its subscription
21787 if let Some(hunk_key) = hunk_key {
21788 let snapshot = self.buffer.read(cx).snapshot(cx);
21789 if let Some(overlay) = self
21790 .diff_review_overlays
21791 .iter_mut()
21792 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21793 {
21794 overlay.inline_edit_editors.remove(&comment_id);
21795 overlay.inline_edit_subscriptions.remove(&comment_id);
21796 }
21797 }
21798
21799 // Clear editing state
21800 self.set_comment_editing(comment_id, false, cx);
21801 }
21802
21803 /// Action handler for ConfirmEditReviewComment.
21804 pub fn confirm_edit_review_comment_action(
21805 &mut self,
21806 action: &ConfirmEditReviewComment,
21807 window: &mut Window,
21808 cx: &mut Context<Self>,
21809 ) {
21810 self.confirm_edit_review_comment(action.id, window, cx);
21811 }
21812
21813 /// Action handler for CancelEditReviewComment.
21814 pub fn cancel_edit_review_comment_action(
21815 &mut self,
21816 action: &CancelEditReviewComment,
21817 window: &mut Window,
21818 cx: &mut Context<Self>,
21819 ) {
21820 self.cancel_edit_review_comment(action.id, window, cx);
21821 }
21822
21823 /// Handles the DeleteReviewComment action - removes a comment.
21824 pub fn delete_review_comment(
21825 &mut self,
21826 action: &DeleteReviewComment,
21827 window: &mut Window,
21828 cx: &mut Context<Self>,
21829 ) {
21830 // Get the hunk key before removing the comment
21831 // Find the hunk key from the comment itself
21832 let comment_id = action.id;
21833 let hunk_key = self
21834 .stored_review_comments
21835 .iter()
21836 .find_map(|(key, comments)| {
21837 if comments.iter().any(|c| c.id == comment_id) {
21838 Some(key.clone())
21839 } else {
21840 None
21841 }
21842 });
21843
21844 // Also get it from the overlay for refresh purposes
21845 let overlay_hunk_key = self
21846 .diff_review_overlays
21847 .first()
21848 .map(|o| o.hunk_key.clone());
21849
21850 self.remove_review_comment(action.id, cx);
21851
21852 // Refresh the overlay height after removing a comment
21853 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
21854 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21855 }
21856 }
21857
21858 fn render_diff_review_overlay(
21859 prompt_editor: &Entity<Editor>,
21860 hunk_key: &DiffHunkKey,
21861 editor_handle: &WeakEntity<Editor>,
21862 cx: &mut BlockContext,
21863 ) -> AnyElement {
21864 let theme = cx.theme();
21865 let colors = theme.colors();
21866
21867 // Get stored comments, expanded state, inline editors, and user avatar from the editor
21868 let (comments, comments_expanded, inline_editors, user_avatar_uri) = editor_handle
21869 .upgrade()
21870 .map(|editor| {
21871 let editor = editor.read(cx);
21872 let snapshot = editor.buffer().read(cx).snapshot(cx);
21873 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
21874 let snapshot = editor.buffer.read(cx).snapshot(cx);
21875 let (expanded, editors, avatar_uri) = editor
21876 .diff_review_overlays
21877 .iter()
21878 .find(|overlay| Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21879 .as_ref()
21880 .map(|o| {
21881 (
21882 o.comments_expanded,
21883 o.inline_edit_editors.clone(),
21884 o.user_avatar_uri.clone(),
21885 )
21886 })
21887 .unwrap_or((true, HashMap::default(), None));
21888 (comments, expanded, editors, avatar_uri)
21889 })
21890 .unwrap_or((Vec::new(), true, HashMap::default(), None));
21891
21892 let comment_count = comments.len();
21893 let avatar_size = px(20.);
21894 let action_icon_size = IconSize::XSmall;
21895
21896 v_flex()
21897 .w_full()
21898 .bg(colors.editor_background)
21899 .border_b_1()
21900 .border_color(colors.border)
21901 .px_2()
21902 .pb_2()
21903 .gap_2()
21904 // Top row: editable input with user's avatar
21905 .child(
21906 h_flex()
21907 .w_full()
21908 .items_center()
21909 .gap_2()
21910 .px_2()
21911 .py_1p5()
21912 .rounded_md()
21913 .bg(colors.surface_background)
21914 .child(
21915 div()
21916 .size(avatar_size)
21917 .flex_shrink_0()
21918 .rounded_full()
21919 .overflow_hidden()
21920 .child(if let Some(ref avatar_uri) = user_avatar_uri {
21921 Avatar::new(avatar_uri.clone())
21922 .size(avatar_size)
21923 .into_any_element()
21924 } else {
21925 Icon::new(IconName::Person)
21926 .size(IconSize::Small)
21927 .color(ui::Color::Muted)
21928 .into_any_element()
21929 }),
21930 )
21931 .child(
21932 div()
21933 .flex_1()
21934 .border_1()
21935 .border_color(colors.border)
21936 .rounded_md()
21937 .bg(colors.editor_background)
21938 .px_2()
21939 .py_1()
21940 .child(prompt_editor.clone()),
21941 )
21942 .child(
21943 h_flex()
21944 .flex_shrink_0()
21945 .gap_1()
21946 .child(
21947 IconButton::new("diff-review-close", IconName::Close)
21948 .icon_color(ui::Color::Muted)
21949 .icon_size(action_icon_size)
21950 .tooltip(Tooltip::text("Close"))
21951 .on_click(|_, window, cx| {
21952 window
21953 .dispatch_action(Box::new(crate::actions::Cancel), cx);
21954 }),
21955 )
21956 .child(
21957 IconButton::new("diff-review-add", IconName::Return)
21958 .icon_color(ui::Color::Muted)
21959 .icon_size(action_icon_size)
21960 .tooltip(Tooltip::text("Add comment"))
21961 .on_click(|_, window, cx| {
21962 window.dispatch_action(
21963 Box::new(crate::actions::SubmitDiffReviewComment),
21964 cx,
21965 );
21966 }),
21967 ),
21968 ),
21969 )
21970 // Expandable comments section (only shown when there are comments)
21971 .when(comment_count > 0, |el| {
21972 el.child(Self::render_comments_section(
21973 comments,
21974 comments_expanded,
21975 inline_editors,
21976 user_avatar_uri,
21977 avatar_size,
21978 action_icon_size,
21979 colors,
21980 ))
21981 })
21982 .into_any_element()
21983 }
21984
21985 fn render_comments_section(
21986 comments: Vec<StoredReviewComment>,
21987 expanded: bool,
21988 inline_editors: HashMap<usize, Entity<Editor>>,
21989 user_avatar_uri: Option<SharedUri>,
21990 avatar_size: Pixels,
21991 action_icon_size: IconSize,
21992 colors: &theme::ThemeColors,
21993 ) -> impl IntoElement {
21994 let comment_count = comments.len();
21995
21996 v_flex()
21997 .w_full()
21998 .gap_1()
21999 // Header with expand/collapse toggle
22000 .child(
22001 h_flex()
22002 .id("review-comments-header")
22003 .w_full()
22004 .items_center()
22005 .gap_1()
22006 .px_2()
22007 .py_1()
22008 .cursor_pointer()
22009 .rounded_md()
22010 .hover(|style| style.bg(colors.ghost_element_hover))
22011 .on_click(|_, window: &mut Window, cx| {
22012 window.dispatch_action(
22013 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22014 cx,
22015 );
22016 })
22017 .child(
22018 Icon::new(if expanded {
22019 IconName::ChevronDown
22020 } else {
22021 IconName::ChevronRight
22022 })
22023 .size(IconSize::Small)
22024 .color(ui::Color::Muted),
22025 )
22026 .child(
22027 Label::new(format!(
22028 "{} Comment{}",
22029 comment_count,
22030 if comment_count == 1 { "" } else { "s" }
22031 ))
22032 .size(LabelSize::Small)
22033 .color(Color::Muted),
22034 ),
22035 )
22036 // Comments list (when expanded)
22037 .when(expanded, |el| {
22038 el.children(comments.into_iter().map(|comment| {
22039 let inline_editor = inline_editors.get(&comment.id).cloned();
22040 Self::render_comment_row(
22041 comment,
22042 inline_editor,
22043 user_avatar_uri.clone(),
22044 avatar_size,
22045 action_icon_size,
22046 colors,
22047 )
22048 }))
22049 })
22050 }
22051
22052 fn render_comment_row(
22053 comment: StoredReviewComment,
22054 inline_editor: Option<Entity<Editor>>,
22055 user_avatar_uri: Option<SharedUri>,
22056 avatar_size: Pixels,
22057 action_icon_size: IconSize,
22058 colors: &theme::ThemeColors,
22059 ) -> impl IntoElement {
22060 let comment_id = comment.id;
22061 let is_editing = inline_editor.is_some();
22062
22063 h_flex()
22064 .w_full()
22065 .items_center()
22066 .gap_2()
22067 .px_2()
22068 .py_1p5()
22069 .rounded_md()
22070 .bg(colors.surface_background)
22071 .child(
22072 div()
22073 .size(avatar_size)
22074 .flex_shrink_0()
22075 .rounded_full()
22076 .overflow_hidden()
22077 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22078 Avatar::new(avatar_uri.clone())
22079 .size(avatar_size)
22080 .into_any_element()
22081 } else {
22082 Icon::new(IconName::Person)
22083 .size(IconSize::Small)
22084 .color(ui::Color::Muted)
22085 .into_any_element()
22086 }),
22087 )
22088 .child(if let Some(editor) = inline_editor {
22089 // Inline edit mode: show an editable text field
22090 div()
22091 .flex_1()
22092 .border_1()
22093 .border_color(colors.border)
22094 .rounded_md()
22095 .bg(colors.editor_background)
22096 .px_2()
22097 .py_1()
22098 .child(editor)
22099 .into_any_element()
22100 } else {
22101 // Display mode: show the comment text
22102 div()
22103 .flex_1()
22104 .text_sm()
22105 .text_color(colors.text)
22106 .child(comment.comment)
22107 .into_any_element()
22108 })
22109 .child(if is_editing {
22110 // Editing mode: show close and confirm buttons
22111 h_flex()
22112 .gap_1()
22113 .child(
22114 IconButton::new(
22115 format!("diff-review-cancel-edit-{comment_id}"),
22116 IconName::Close,
22117 )
22118 .icon_color(ui::Color::Muted)
22119 .icon_size(action_icon_size)
22120 .tooltip(Tooltip::text("Cancel"))
22121 .on_click(move |_, window, cx| {
22122 window.dispatch_action(
22123 Box::new(crate::actions::CancelEditReviewComment {
22124 id: comment_id,
22125 }),
22126 cx,
22127 );
22128 }),
22129 )
22130 .child(
22131 IconButton::new(
22132 format!("diff-review-confirm-edit-{comment_id}"),
22133 IconName::Return,
22134 )
22135 .icon_color(ui::Color::Muted)
22136 .icon_size(action_icon_size)
22137 .tooltip(Tooltip::text("Confirm"))
22138 .on_click(move |_, window, cx| {
22139 window.dispatch_action(
22140 Box::new(crate::actions::ConfirmEditReviewComment {
22141 id: comment_id,
22142 }),
22143 cx,
22144 );
22145 }),
22146 )
22147 .into_any_element()
22148 } else {
22149 // Display mode: no action buttons for now (edit/delete not yet implemented)
22150 gpui::Empty.into_any_element()
22151 })
22152 }
22153
22154 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22155 if self.display_map.read(cx).masked != masked {
22156 self.display_map.update(cx, |map, _| map.masked = masked);
22157 }
22158 cx.notify()
22159 }
22160
22161 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22162 self.show_wrap_guides = Some(show_wrap_guides);
22163 cx.notify();
22164 }
22165
22166 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22167 self.show_indent_guides = Some(show_indent_guides);
22168 cx.notify();
22169 }
22170
22171 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22172 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22173 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22174 && let Some(dir) = file.abs_path(cx).parent()
22175 {
22176 return Some(dir.to_owned());
22177 }
22178 }
22179
22180 None
22181 }
22182
22183 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22184 self.active_excerpt(cx)?
22185 .1
22186 .read(cx)
22187 .file()
22188 .and_then(|f| f.as_local())
22189 }
22190
22191 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22192 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22193 let buffer = buffer.read(cx);
22194 if let Some(project_path) = buffer.project_path(cx) {
22195 let project = self.project()?.read(cx);
22196 project.absolute_path(&project_path, cx)
22197 } else {
22198 buffer
22199 .file()
22200 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22201 }
22202 })
22203 }
22204
22205 pub fn reveal_in_finder(
22206 &mut self,
22207 _: &RevealInFileManager,
22208 _window: &mut Window,
22209 cx: &mut Context<Self>,
22210 ) {
22211 if let Some(target) = self.target_file(cx) {
22212 cx.reveal_path(&target.abs_path(cx));
22213 }
22214 }
22215
22216 pub fn copy_path(
22217 &mut self,
22218 _: &zed_actions::workspace::CopyPath,
22219 _window: &mut Window,
22220 cx: &mut Context<Self>,
22221 ) {
22222 if let Some(path) = self.target_file_abs_path(cx)
22223 && let Some(path) = path.to_str()
22224 {
22225 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22226 } else {
22227 cx.propagate();
22228 }
22229 }
22230
22231 pub fn copy_relative_path(
22232 &mut self,
22233 _: &zed_actions::workspace::CopyRelativePath,
22234 _window: &mut Window,
22235 cx: &mut Context<Self>,
22236 ) {
22237 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22238 let project = self.project()?.read(cx);
22239 let path = buffer.read(cx).file()?.path();
22240 let path = path.display(project.path_style(cx));
22241 Some(path)
22242 }) {
22243 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22244 } else {
22245 cx.propagate();
22246 }
22247 }
22248
22249 /// Returns the project path for the editor's buffer, if any buffer is
22250 /// opened in the editor.
22251 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22252 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22253 buffer.read(cx).project_path(cx)
22254 } else {
22255 None
22256 }
22257 }
22258
22259 // Returns true if the editor handled a go-to-line request
22260 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22261 maybe!({
22262 let breakpoint_store = self.breakpoint_store.as_ref()?;
22263
22264 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22265 else {
22266 self.clear_row_highlights::<ActiveDebugLine>();
22267 return None;
22268 };
22269
22270 let position = active_stack_frame.position;
22271 let buffer_id = position.buffer_id?;
22272 let snapshot = self
22273 .project
22274 .as_ref()?
22275 .read(cx)
22276 .buffer_for_id(buffer_id, cx)?
22277 .read(cx)
22278 .snapshot();
22279
22280 let mut handled = false;
22281 for (id, ExcerptRange { context, .. }) in
22282 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22283 {
22284 if context.start.cmp(&position, &snapshot).is_ge()
22285 || context.end.cmp(&position, &snapshot).is_lt()
22286 {
22287 continue;
22288 }
22289 let snapshot = self.buffer.read(cx).snapshot(cx);
22290 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22291
22292 handled = true;
22293 self.clear_row_highlights::<ActiveDebugLine>();
22294
22295 self.go_to_line::<ActiveDebugLine>(
22296 multibuffer_anchor,
22297 Some(cx.theme().colors().editor_debugger_active_line_background),
22298 window,
22299 cx,
22300 );
22301
22302 cx.notify();
22303 }
22304
22305 handled.then_some(())
22306 })
22307 .is_some()
22308 }
22309
22310 pub fn copy_file_name_without_extension(
22311 &mut self,
22312 _: &CopyFileNameWithoutExtension,
22313 _: &mut Window,
22314 cx: &mut Context<Self>,
22315 ) {
22316 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22317 let file = buffer.read(cx).file()?;
22318 file.path().file_stem()
22319 }) {
22320 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22321 }
22322 }
22323
22324 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22325 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22326 let file = buffer.read(cx).file()?;
22327 Some(file.file_name(cx))
22328 }) {
22329 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22330 }
22331 }
22332
22333 pub fn toggle_git_blame(
22334 &mut self,
22335 _: &::git::Blame,
22336 window: &mut Window,
22337 cx: &mut Context<Self>,
22338 ) {
22339 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22340
22341 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22342 self.start_git_blame(true, window, cx);
22343 }
22344
22345 cx.notify();
22346 }
22347
22348 pub fn toggle_git_blame_inline(
22349 &mut self,
22350 _: &ToggleGitBlameInline,
22351 window: &mut Window,
22352 cx: &mut Context<Self>,
22353 ) {
22354 self.toggle_git_blame_inline_internal(true, window, cx);
22355 cx.notify();
22356 }
22357
22358 pub fn open_git_blame_commit(
22359 &mut self,
22360 _: &OpenGitBlameCommit,
22361 window: &mut Window,
22362 cx: &mut Context<Self>,
22363 ) {
22364 self.open_git_blame_commit_internal(window, cx);
22365 }
22366
22367 fn open_git_blame_commit_internal(
22368 &mut self,
22369 window: &mut Window,
22370 cx: &mut Context<Self>,
22371 ) -> Option<()> {
22372 let blame = self.blame.as_ref()?;
22373 let snapshot = self.snapshot(window, cx);
22374 let cursor = self
22375 .selections
22376 .newest::<Point>(&snapshot.display_snapshot)
22377 .head();
22378 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22379 let (_, blame_entry) = blame
22380 .update(cx, |blame, cx| {
22381 blame
22382 .blame_for_rows(
22383 &[RowInfo {
22384 buffer_id: Some(buffer.remote_id()),
22385 buffer_row: Some(point.row),
22386 ..Default::default()
22387 }],
22388 cx,
22389 )
22390 .next()
22391 })
22392 .flatten()?;
22393 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22394 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22395 let workspace = self.workspace()?.downgrade();
22396 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22397 None
22398 }
22399
22400 pub fn git_blame_inline_enabled(&self) -> bool {
22401 self.git_blame_inline_enabled
22402 }
22403
22404 pub fn toggle_selection_menu(
22405 &mut self,
22406 _: &ToggleSelectionMenu,
22407 _: &mut Window,
22408 cx: &mut Context<Self>,
22409 ) {
22410 self.show_selection_menu = self
22411 .show_selection_menu
22412 .map(|show_selections_menu| !show_selections_menu)
22413 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22414
22415 cx.notify();
22416 }
22417
22418 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22419 self.show_selection_menu
22420 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22421 }
22422
22423 fn start_git_blame(
22424 &mut self,
22425 user_triggered: bool,
22426 window: &mut Window,
22427 cx: &mut Context<Self>,
22428 ) {
22429 if let Some(project) = self.project() {
22430 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22431 && buffer.read(cx).file().is_none()
22432 {
22433 return;
22434 }
22435
22436 let focused = self.focus_handle(cx).contains_focused(window, cx);
22437
22438 let project = project.clone();
22439 let blame = cx
22440 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22441 self.blame_subscription =
22442 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22443 self.blame = Some(blame);
22444 }
22445 }
22446
22447 fn toggle_git_blame_inline_internal(
22448 &mut self,
22449 user_triggered: bool,
22450 window: &mut Window,
22451 cx: &mut Context<Self>,
22452 ) {
22453 if self.git_blame_inline_enabled {
22454 self.git_blame_inline_enabled = false;
22455 self.show_git_blame_inline = false;
22456 self.show_git_blame_inline_delay_task.take();
22457 } else {
22458 self.git_blame_inline_enabled = true;
22459 self.start_git_blame_inline(user_triggered, window, cx);
22460 }
22461
22462 cx.notify();
22463 }
22464
22465 fn start_git_blame_inline(
22466 &mut self,
22467 user_triggered: bool,
22468 window: &mut Window,
22469 cx: &mut Context<Self>,
22470 ) {
22471 self.start_git_blame(user_triggered, window, cx);
22472
22473 if ProjectSettings::get_global(cx)
22474 .git
22475 .inline_blame_delay()
22476 .is_some()
22477 {
22478 self.start_inline_blame_timer(window, cx);
22479 } else {
22480 self.show_git_blame_inline = true
22481 }
22482 }
22483
22484 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22485 self.blame.as_ref()
22486 }
22487
22488 pub fn show_git_blame_gutter(&self) -> bool {
22489 self.show_git_blame_gutter
22490 }
22491
22492 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22493 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22494 }
22495
22496 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22497 self.show_git_blame_inline
22498 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22499 && !self.newest_selection_head_on_empty_line(cx)
22500 && self.has_blame_entries(cx)
22501 }
22502
22503 fn has_blame_entries(&self, cx: &App) -> bool {
22504 self.blame()
22505 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22506 }
22507
22508 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22509 let cursor_anchor = self.selections.newest_anchor().head();
22510
22511 let snapshot = self.buffer.read(cx).snapshot(cx);
22512 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22513
22514 snapshot.line_len(buffer_row) == 0
22515 }
22516
22517 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22518 let buffer_and_selection = maybe!({
22519 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22520 let selection_range = selection.range();
22521
22522 let multi_buffer = self.buffer().read(cx);
22523 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22524 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
22525
22526 let (buffer, range, _) = if selection.reversed {
22527 buffer_ranges.first()
22528 } else {
22529 buffer_ranges.last()
22530 }?;
22531
22532 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
22533 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
22534
22535 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22536 let selection = start_row_in_buffer..end_row_in_buffer;
22537
22538 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
22539 };
22540
22541 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22542
22543 Some((
22544 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22545 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, Bias::Left, buffer)
22546 ..buffer_diff_snapshot.row_to_base_text_row(
22547 end_row_in_buffer,
22548 Bias::Left,
22549 buffer,
22550 ),
22551 ))
22552 });
22553
22554 let Some((buffer, selection)) = buffer_and_selection else {
22555 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22556 };
22557
22558 let Some(project) = self.project() else {
22559 return Task::ready(Err(anyhow!("editor does not have project")));
22560 };
22561
22562 project.update(cx, |project, cx| {
22563 project.get_permalink_to_line(&buffer, selection, cx)
22564 })
22565 }
22566
22567 pub fn copy_permalink_to_line(
22568 &mut self,
22569 _: &CopyPermalinkToLine,
22570 window: &mut Window,
22571 cx: &mut Context<Self>,
22572 ) {
22573 let permalink_task = self.get_permalink_to_line(cx);
22574 let workspace = self.workspace();
22575
22576 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22577 Ok(permalink) => {
22578 cx.update(|_, cx| {
22579 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22580 })
22581 .ok();
22582 }
22583 Err(err) => {
22584 let message = format!("Failed to copy permalink: {err}");
22585
22586 anyhow::Result::<()>::Err(err).log_err();
22587
22588 if let Some(workspace) = workspace {
22589 workspace
22590 .update_in(cx, |workspace, _, cx| {
22591 struct CopyPermalinkToLine;
22592
22593 workspace.show_toast(
22594 Toast::new(
22595 NotificationId::unique::<CopyPermalinkToLine>(),
22596 message,
22597 ),
22598 cx,
22599 )
22600 })
22601 .ok();
22602 }
22603 }
22604 })
22605 .detach();
22606 }
22607
22608 pub fn copy_file_location(
22609 &mut self,
22610 _: &CopyFileLocation,
22611 _: &mut Window,
22612 cx: &mut Context<Self>,
22613 ) {
22614 let selection = self
22615 .selections
22616 .newest::<Point>(&self.display_snapshot(cx))
22617 .start
22618 .row
22619 + 1;
22620 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22621 let project = self.project()?.read(cx);
22622 let file = buffer.read(cx).file()?;
22623 let path = file.path().display(project.path_style(cx));
22624
22625 Some(format!("{path}:{selection}"))
22626 }) {
22627 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
22628 }
22629 }
22630
22631 pub fn open_permalink_to_line(
22632 &mut self,
22633 _: &OpenPermalinkToLine,
22634 window: &mut Window,
22635 cx: &mut Context<Self>,
22636 ) {
22637 let permalink_task = self.get_permalink_to_line(cx);
22638 let workspace = self.workspace();
22639
22640 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22641 Ok(permalink) => {
22642 cx.update(|_, cx| {
22643 cx.open_url(permalink.as_ref());
22644 })
22645 .ok();
22646 }
22647 Err(err) => {
22648 let message = format!("Failed to open permalink: {err}");
22649
22650 anyhow::Result::<()>::Err(err).log_err();
22651
22652 if let Some(workspace) = workspace {
22653 workspace.update(cx, |workspace, cx| {
22654 struct OpenPermalinkToLine;
22655
22656 workspace.show_toast(
22657 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
22658 cx,
22659 )
22660 });
22661 }
22662 }
22663 })
22664 .detach();
22665 }
22666
22667 pub fn insert_uuid_v4(
22668 &mut self,
22669 _: &InsertUuidV4,
22670 window: &mut Window,
22671 cx: &mut Context<Self>,
22672 ) {
22673 self.insert_uuid(UuidVersion::V4, window, cx);
22674 }
22675
22676 pub fn insert_uuid_v7(
22677 &mut self,
22678 _: &InsertUuidV7,
22679 window: &mut Window,
22680 cx: &mut Context<Self>,
22681 ) {
22682 self.insert_uuid(UuidVersion::V7, window, cx);
22683 }
22684
22685 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
22686 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
22687 self.transact(window, cx, |this, window, cx| {
22688 let edits = this
22689 .selections
22690 .all::<Point>(&this.display_snapshot(cx))
22691 .into_iter()
22692 .map(|selection| {
22693 let uuid = match version {
22694 UuidVersion::V4 => uuid::Uuid::new_v4(),
22695 UuidVersion::V7 => uuid::Uuid::now_v7(),
22696 };
22697
22698 (selection.range(), uuid.to_string())
22699 });
22700 this.edit(edits, cx);
22701 this.refresh_edit_prediction(true, false, window, cx);
22702 });
22703 }
22704
22705 pub fn open_selections_in_multibuffer(
22706 &mut self,
22707 _: &OpenSelectionsInMultibuffer,
22708 window: &mut Window,
22709 cx: &mut Context<Self>,
22710 ) {
22711 let multibuffer = self.buffer.read(cx);
22712
22713 let Some(buffer) = multibuffer.as_singleton() else {
22714 return;
22715 };
22716
22717 let Some(workspace) = self.workspace() else {
22718 return;
22719 };
22720
22721 let title = multibuffer.title(cx).to_string();
22722
22723 let locations = self
22724 .selections
22725 .all_anchors(&self.display_snapshot(cx))
22726 .iter()
22727 .map(|selection| {
22728 (
22729 buffer.clone(),
22730 (selection.start.text_anchor..selection.end.text_anchor)
22731 .to_point(buffer.read(cx)),
22732 )
22733 })
22734 .into_group_map();
22735
22736 cx.spawn_in(window, async move |_, cx| {
22737 workspace.update_in(cx, |workspace, window, cx| {
22738 Self::open_locations_in_multibuffer(
22739 workspace,
22740 locations,
22741 format!("Selections for '{title}'"),
22742 false,
22743 false,
22744 MultibufferSelectionMode::All,
22745 window,
22746 cx,
22747 );
22748 })
22749 })
22750 .detach();
22751 }
22752
22753 /// Adds a row highlight for the given range. If a row has multiple highlights, the
22754 /// last highlight added will be used.
22755 ///
22756 /// If the range ends at the beginning of a line, then that line will not be highlighted.
22757 pub fn highlight_rows<T: 'static>(
22758 &mut self,
22759 range: Range<Anchor>,
22760 color: Hsla,
22761 options: RowHighlightOptions,
22762 cx: &mut Context<Self>,
22763 ) {
22764 let snapshot = self.buffer().read(cx).snapshot(cx);
22765 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
22766 let ix = row_highlights.binary_search_by(|highlight| {
22767 Ordering::Equal
22768 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
22769 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
22770 });
22771
22772 if let Err(mut ix) = ix {
22773 let index = post_inc(&mut self.highlight_order);
22774
22775 // If this range intersects with the preceding highlight, then merge it with
22776 // the preceding highlight. Otherwise insert a new highlight.
22777 let mut merged = false;
22778 if ix > 0 {
22779 let prev_highlight = &mut row_highlights[ix - 1];
22780 if prev_highlight
22781 .range
22782 .end
22783 .cmp(&range.start, &snapshot)
22784 .is_ge()
22785 {
22786 ix -= 1;
22787 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
22788 prev_highlight.range.end = range.end;
22789 }
22790 merged = true;
22791 prev_highlight.index = index;
22792 prev_highlight.color = color;
22793 prev_highlight.options = options;
22794 }
22795 }
22796
22797 if !merged {
22798 row_highlights.insert(
22799 ix,
22800 RowHighlight {
22801 range,
22802 index,
22803 color,
22804 options,
22805 type_id: TypeId::of::<T>(),
22806 },
22807 );
22808 }
22809
22810 // If any of the following highlights intersect with this one, merge them.
22811 while let Some(next_highlight) = row_highlights.get(ix + 1) {
22812 let highlight = &row_highlights[ix];
22813 if next_highlight
22814 .range
22815 .start
22816 .cmp(&highlight.range.end, &snapshot)
22817 .is_le()
22818 {
22819 if next_highlight
22820 .range
22821 .end
22822 .cmp(&highlight.range.end, &snapshot)
22823 .is_gt()
22824 {
22825 row_highlights[ix].range.end = next_highlight.range.end;
22826 }
22827 row_highlights.remove(ix + 1);
22828 } else {
22829 break;
22830 }
22831 }
22832 }
22833 }
22834
22835 /// Remove any highlighted row ranges of the given type that intersect the
22836 /// given ranges.
22837 pub fn remove_highlighted_rows<T: 'static>(
22838 &mut self,
22839 ranges_to_remove: Vec<Range<Anchor>>,
22840 cx: &mut Context<Self>,
22841 ) {
22842 let snapshot = self.buffer().read(cx).snapshot(cx);
22843 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
22844 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
22845 row_highlights.retain(|highlight| {
22846 while let Some(range_to_remove) = ranges_to_remove.peek() {
22847 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
22848 Ordering::Less | Ordering::Equal => {
22849 ranges_to_remove.next();
22850 }
22851 Ordering::Greater => {
22852 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
22853 Ordering::Less | Ordering::Equal => {
22854 return false;
22855 }
22856 Ordering::Greater => break,
22857 }
22858 }
22859 }
22860 }
22861
22862 true
22863 })
22864 }
22865
22866 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
22867 pub fn clear_row_highlights<T: 'static>(&mut self) {
22868 self.highlighted_rows.remove(&TypeId::of::<T>());
22869 }
22870
22871 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
22872 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
22873 self.highlighted_rows
22874 .get(&TypeId::of::<T>())
22875 .map_or(&[] as &[_], |vec| vec.as_slice())
22876 .iter()
22877 .map(|highlight| (highlight.range.clone(), highlight.color))
22878 }
22879
22880 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
22881 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
22882 /// Allows to ignore certain kinds of highlights.
22883 pub fn highlighted_display_rows(
22884 &self,
22885 window: &mut Window,
22886 cx: &mut App,
22887 ) -> BTreeMap<DisplayRow, LineHighlight> {
22888 let snapshot = self.snapshot(window, cx);
22889 let mut used_highlight_orders = HashMap::default();
22890 self.highlighted_rows
22891 .iter()
22892 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
22893 .fold(
22894 BTreeMap::<DisplayRow, LineHighlight>::new(),
22895 |mut unique_rows, highlight| {
22896 let start = highlight.range.start.to_display_point(&snapshot);
22897 let end = highlight.range.end.to_display_point(&snapshot);
22898 let start_row = start.row().0;
22899 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
22900 {
22901 end.row().0.saturating_sub(1)
22902 } else {
22903 end.row().0
22904 };
22905 for row in start_row..=end_row {
22906 let used_index =
22907 used_highlight_orders.entry(row).or_insert(highlight.index);
22908 if highlight.index >= *used_index {
22909 *used_index = highlight.index;
22910 unique_rows.insert(
22911 DisplayRow(row),
22912 LineHighlight {
22913 include_gutter: highlight.options.include_gutter,
22914 border: None,
22915 background: highlight.color.into(),
22916 type_id: Some(highlight.type_id),
22917 },
22918 );
22919 }
22920 }
22921 unique_rows
22922 },
22923 )
22924 }
22925
22926 pub fn highlighted_display_row_for_autoscroll(
22927 &self,
22928 snapshot: &DisplaySnapshot,
22929 ) -> Option<DisplayRow> {
22930 self.highlighted_rows
22931 .values()
22932 .flat_map(|highlighted_rows| highlighted_rows.iter())
22933 .filter_map(|highlight| {
22934 if highlight.options.autoscroll {
22935 Some(highlight.range.start.to_display_point(snapshot).row())
22936 } else {
22937 None
22938 }
22939 })
22940 .min()
22941 }
22942
22943 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
22944 self.highlight_background::<SearchWithinRange>(
22945 ranges,
22946 |_, colors| colors.colors().editor_document_highlight_read_background,
22947 cx,
22948 )
22949 }
22950
22951 pub fn set_breadcrumb_header(&mut self, new_header: String) {
22952 self.breadcrumb_header = Some(new_header);
22953 }
22954
22955 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
22956 self.clear_background_highlights::<SearchWithinRange>(cx);
22957 }
22958
22959 pub fn highlight_background<T: 'static>(
22960 &mut self,
22961 ranges: &[Range<Anchor>],
22962 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
22963 cx: &mut Context<Self>,
22964 ) {
22965 self.background_highlights.insert(
22966 HighlightKey::Type(TypeId::of::<T>()),
22967 (Arc::new(color_fetcher), Arc::from(ranges)),
22968 );
22969 self.scrollbar_marker_state.dirty = true;
22970 cx.notify();
22971 }
22972
22973 pub fn highlight_background_key<T: 'static>(
22974 &mut self,
22975 key: usize,
22976 ranges: &[Range<Anchor>],
22977 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
22978 cx: &mut Context<Self>,
22979 ) {
22980 self.background_highlights.insert(
22981 HighlightKey::TypePlus(TypeId::of::<T>(), key),
22982 (Arc::new(color_fetcher), Arc::from(ranges)),
22983 );
22984 self.scrollbar_marker_state.dirty = true;
22985 cx.notify();
22986 }
22987
22988 pub fn clear_background_highlights<T: 'static>(
22989 &mut self,
22990 cx: &mut Context<Self>,
22991 ) -> Option<BackgroundHighlight> {
22992 let text_highlights = self
22993 .background_highlights
22994 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
22995 if !text_highlights.1.is_empty() {
22996 self.scrollbar_marker_state.dirty = true;
22997 cx.notify();
22998 }
22999 Some(text_highlights)
23000 }
23001
23002 pub fn highlight_gutter<T: 'static>(
23003 &mut self,
23004 ranges: impl Into<Vec<Range<Anchor>>>,
23005 color_fetcher: fn(&App) -> Hsla,
23006 cx: &mut Context<Self>,
23007 ) {
23008 self.gutter_highlights
23009 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23010 cx.notify();
23011 }
23012
23013 pub fn clear_gutter_highlights<T: 'static>(
23014 &mut self,
23015 cx: &mut Context<Self>,
23016 ) -> Option<GutterHighlight> {
23017 cx.notify();
23018 self.gutter_highlights.remove(&TypeId::of::<T>())
23019 }
23020
23021 pub fn insert_gutter_highlight<T: 'static>(
23022 &mut self,
23023 range: Range<Anchor>,
23024 color_fetcher: fn(&App) -> Hsla,
23025 cx: &mut Context<Self>,
23026 ) {
23027 let snapshot = self.buffer().read(cx).snapshot(cx);
23028 let mut highlights = self
23029 .gutter_highlights
23030 .remove(&TypeId::of::<T>())
23031 .map(|(_, highlights)| highlights)
23032 .unwrap_or_default();
23033 let ix = highlights.binary_search_by(|highlight| {
23034 Ordering::Equal
23035 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23036 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23037 });
23038 if let Err(ix) = ix {
23039 highlights.insert(ix, range);
23040 }
23041 self.gutter_highlights
23042 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23043 }
23044
23045 pub fn remove_gutter_highlights<T: 'static>(
23046 &mut self,
23047 ranges_to_remove: Vec<Range<Anchor>>,
23048 cx: &mut Context<Self>,
23049 ) {
23050 let snapshot = self.buffer().read(cx).snapshot(cx);
23051 let Some((color_fetcher, mut gutter_highlights)) =
23052 self.gutter_highlights.remove(&TypeId::of::<T>())
23053 else {
23054 return;
23055 };
23056 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23057 gutter_highlights.retain(|highlight| {
23058 while let Some(range_to_remove) = ranges_to_remove.peek() {
23059 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23060 Ordering::Less | Ordering::Equal => {
23061 ranges_to_remove.next();
23062 }
23063 Ordering::Greater => {
23064 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23065 Ordering::Less | Ordering::Equal => {
23066 return false;
23067 }
23068 Ordering::Greater => break,
23069 }
23070 }
23071 }
23072 }
23073
23074 true
23075 });
23076 self.gutter_highlights
23077 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23078 }
23079
23080 #[cfg(feature = "test-support")]
23081 pub fn all_text_highlights(
23082 &self,
23083 window: &mut Window,
23084 cx: &mut Context<Self>,
23085 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23086 let snapshot = self.snapshot(window, cx);
23087 self.display_map.update(cx, |display_map, _| {
23088 display_map
23089 .all_text_highlights()
23090 .map(|highlight| {
23091 let (style, ranges) = highlight.as_ref();
23092 (
23093 *style,
23094 ranges
23095 .iter()
23096 .map(|range| range.clone().to_display_points(&snapshot))
23097 .collect(),
23098 )
23099 })
23100 .collect()
23101 })
23102 }
23103
23104 #[cfg(feature = "test-support")]
23105 pub fn all_text_background_highlights(
23106 &self,
23107 window: &mut Window,
23108 cx: &mut Context<Self>,
23109 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23110 let snapshot = self.snapshot(window, cx);
23111 let buffer = &snapshot.buffer_snapshot();
23112 let start = buffer.anchor_before(MultiBufferOffset(0));
23113 let end = buffer.anchor_after(buffer.len());
23114 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23115 }
23116
23117 #[cfg(any(test, feature = "test-support"))]
23118 pub fn sorted_background_highlights_in_range(
23119 &self,
23120 search_range: Range<Anchor>,
23121 display_snapshot: &DisplaySnapshot,
23122 theme: &Theme,
23123 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23124 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23125 res.sort_by(|a, b| {
23126 a.0.start
23127 .cmp(&b.0.start)
23128 .then_with(|| a.0.end.cmp(&b.0.end))
23129 .then_with(|| a.1.cmp(&b.1))
23130 });
23131 res
23132 }
23133
23134 #[cfg(feature = "test-support")]
23135 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23136 let snapshot = self.buffer().read(cx).snapshot(cx);
23137
23138 let highlights = self
23139 .background_highlights
23140 .get(&HighlightKey::Type(TypeId::of::<
23141 items::BufferSearchHighlights,
23142 >()));
23143
23144 if let Some((_color, ranges)) = highlights {
23145 ranges
23146 .iter()
23147 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23148 .collect_vec()
23149 } else {
23150 vec![]
23151 }
23152 }
23153
23154 fn document_highlights_for_position<'a>(
23155 &'a self,
23156 position: Anchor,
23157 buffer: &'a MultiBufferSnapshot,
23158 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23159 let read_highlights = self
23160 .background_highlights
23161 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
23162 .map(|h| &h.1);
23163 let write_highlights = self
23164 .background_highlights
23165 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
23166 .map(|h| &h.1);
23167 let left_position = position.bias_left(buffer);
23168 let right_position = position.bias_right(buffer);
23169 read_highlights
23170 .into_iter()
23171 .chain(write_highlights)
23172 .flat_map(move |ranges| {
23173 let start_ix = match ranges.binary_search_by(|probe| {
23174 let cmp = probe.end.cmp(&left_position, buffer);
23175 if cmp.is_ge() {
23176 Ordering::Greater
23177 } else {
23178 Ordering::Less
23179 }
23180 }) {
23181 Ok(i) | Err(i) => i,
23182 };
23183
23184 ranges[start_ix..]
23185 .iter()
23186 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23187 })
23188 }
23189
23190 pub fn has_background_highlights<T: 'static>(&self) -> bool {
23191 self.background_highlights
23192 .get(&HighlightKey::Type(TypeId::of::<T>()))
23193 .is_some_and(|(_, highlights)| !highlights.is_empty())
23194 }
23195
23196 /// Returns all background highlights for a given range.
23197 ///
23198 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23199 pub fn background_highlights_in_range(
23200 &self,
23201 search_range: Range<Anchor>,
23202 display_snapshot: &DisplaySnapshot,
23203 theme: &Theme,
23204 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23205 let mut results = Vec::new();
23206 for (color_fetcher, ranges) in self.background_highlights.values() {
23207 let start_ix = match ranges.binary_search_by(|probe| {
23208 let cmp = probe
23209 .end
23210 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23211 if cmp.is_gt() {
23212 Ordering::Greater
23213 } else {
23214 Ordering::Less
23215 }
23216 }) {
23217 Ok(i) | Err(i) => i,
23218 };
23219 for (index, range) in ranges[start_ix..].iter().enumerate() {
23220 if range
23221 .start
23222 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23223 .is_ge()
23224 {
23225 break;
23226 }
23227
23228 let color = color_fetcher(&(start_ix + index), theme);
23229 let start = range.start.to_display_point(display_snapshot);
23230 let end = range.end.to_display_point(display_snapshot);
23231 results.push((start..end, color))
23232 }
23233 }
23234 results
23235 }
23236
23237 pub fn gutter_highlights_in_range(
23238 &self,
23239 search_range: Range<Anchor>,
23240 display_snapshot: &DisplaySnapshot,
23241 cx: &App,
23242 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23243 let mut results = Vec::new();
23244 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23245 let color = color_fetcher(cx);
23246 let start_ix = match ranges.binary_search_by(|probe| {
23247 let cmp = probe
23248 .end
23249 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23250 if cmp.is_gt() {
23251 Ordering::Greater
23252 } else {
23253 Ordering::Less
23254 }
23255 }) {
23256 Ok(i) | Err(i) => i,
23257 };
23258 for range in &ranges[start_ix..] {
23259 if range
23260 .start
23261 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23262 .is_ge()
23263 {
23264 break;
23265 }
23266
23267 let start = range.start.to_display_point(display_snapshot);
23268 let end = range.end.to_display_point(display_snapshot);
23269 results.push((start..end, color))
23270 }
23271 }
23272 results
23273 }
23274
23275 /// Get the text ranges corresponding to the redaction query
23276 pub fn redacted_ranges(
23277 &self,
23278 search_range: Range<Anchor>,
23279 display_snapshot: &DisplaySnapshot,
23280 cx: &App,
23281 ) -> Vec<Range<DisplayPoint>> {
23282 display_snapshot
23283 .buffer_snapshot()
23284 .redacted_ranges(search_range, |file| {
23285 if let Some(file) = file {
23286 file.is_private()
23287 && EditorSettings::get(
23288 Some(SettingsLocation {
23289 worktree_id: file.worktree_id(cx),
23290 path: file.path().as_ref(),
23291 }),
23292 cx,
23293 )
23294 .redact_private_values
23295 } else {
23296 false
23297 }
23298 })
23299 .map(|range| {
23300 range.start.to_display_point(display_snapshot)
23301 ..range.end.to_display_point(display_snapshot)
23302 })
23303 .collect()
23304 }
23305
23306 pub fn highlight_text_key<T: 'static>(
23307 &mut self,
23308 key: usize,
23309 ranges: Vec<Range<Anchor>>,
23310 style: HighlightStyle,
23311 merge: bool,
23312 cx: &mut Context<Self>,
23313 ) {
23314 self.display_map.update(cx, |map, cx| {
23315 map.highlight_text(
23316 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23317 ranges,
23318 style,
23319 merge,
23320 cx,
23321 );
23322 });
23323 cx.notify();
23324 }
23325
23326 pub fn highlight_text<T: 'static>(
23327 &mut self,
23328 ranges: Vec<Range<Anchor>>,
23329 style: HighlightStyle,
23330 cx: &mut Context<Self>,
23331 ) {
23332 self.display_map.update(cx, |map, cx| {
23333 map.highlight_text(
23334 HighlightKey::Type(TypeId::of::<T>()),
23335 ranges,
23336 style,
23337 false,
23338 cx,
23339 )
23340 });
23341 cx.notify();
23342 }
23343
23344 pub fn text_highlights<'a, T: 'static>(
23345 &'a self,
23346 cx: &'a App,
23347 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23348 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
23349 }
23350
23351 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
23352 let cleared = self
23353 .display_map
23354 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
23355 if cleared {
23356 cx.notify();
23357 }
23358 }
23359
23360 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23361 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23362 && self.focus_handle.is_focused(window)
23363 }
23364
23365 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23366 self.show_cursor_when_unfocused = is_enabled;
23367 cx.notify();
23368 }
23369
23370 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23371 cx.notify();
23372 }
23373
23374 fn on_debug_session_event(
23375 &mut self,
23376 _session: Entity<Session>,
23377 event: &SessionEvent,
23378 cx: &mut Context<Self>,
23379 ) {
23380 if let SessionEvent::InvalidateInlineValue = event {
23381 self.refresh_inline_values(cx);
23382 }
23383 }
23384
23385 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23386 let Some(project) = self.project.clone() else {
23387 return;
23388 };
23389
23390 if !self.inline_value_cache.enabled {
23391 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23392 self.splice_inlays(&inlays, Vec::new(), cx);
23393 return;
23394 }
23395
23396 let current_execution_position = self
23397 .highlighted_rows
23398 .get(&TypeId::of::<ActiveDebugLine>())
23399 .and_then(|lines| lines.last().map(|line| line.range.end));
23400
23401 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23402 let inline_values = editor
23403 .update(cx, |editor, cx| {
23404 let Some(current_execution_position) = current_execution_position else {
23405 return Some(Task::ready(Ok(Vec::new())));
23406 };
23407
23408 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23409 let snapshot = buffer.snapshot(cx);
23410
23411 let excerpt = snapshot.excerpt_containing(
23412 current_execution_position..current_execution_position,
23413 )?;
23414
23415 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23416 })?;
23417
23418 let range =
23419 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23420
23421 project.inline_values(buffer, range, cx)
23422 })
23423 .ok()
23424 .flatten()?
23425 .await
23426 .context("refreshing debugger inlays")
23427 .log_err()?;
23428
23429 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23430
23431 for (buffer_id, inline_value) in inline_values
23432 .into_iter()
23433 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23434 {
23435 buffer_inline_values
23436 .entry(buffer_id)
23437 .or_default()
23438 .push(inline_value);
23439 }
23440
23441 editor
23442 .update(cx, |editor, cx| {
23443 let snapshot = editor.buffer.read(cx).snapshot(cx);
23444 let mut new_inlays = Vec::default();
23445
23446 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23447 let buffer_id = buffer_snapshot.remote_id();
23448 buffer_inline_values
23449 .get(&buffer_id)
23450 .into_iter()
23451 .flatten()
23452 .for_each(|hint| {
23453 let inlay = Inlay::debugger(
23454 post_inc(&mut editor.next_inlay_id),
23455 Anchor::in_buffer(excerpt_id, hint.position),
23456 hint.text(),
23457 );
23458 if !inlay.text().chars().contains(&'\n') {
23459 new_inlays.push(inlay);
23460 }
23461 });
23462 }
23463
23464 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23465 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23466
23467 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23468 })
23469 .ok()?;
23470 Some(())
23471 });
23472 }
23473
23474 fn on_buffer_event(
23475 &mut self,
23476 multibuffer: &Entity<MultiBuffer>,
23477 event: &multi_buffer::Event,
23478 window: &mut Window,
23479 cx: &mut Context<Self>,
23480 ) {
23481 match event {
23482 multi_buffer::Event::Edited { edited_buffer } => {
23483 self.scrollbar_marker_state.dirty = true;
23484 self.active_indent_guides_state.dirty = true;
23485 self.refresh_active_diagnostics(cx);
23486 self.refresh_code_actions(window, cx);
23487 self.refresh_single_line_folds(window, cx);
23488 self.refresh_matching_bracket_highlights(window, cx);
23489 if self.has_active_edit_prediction() {
23490 self.update_visible_edit_prediction(window, cx);
23491 }
23492
23493 // Clean up orphaned review comments after edits
23494 self.cleanup_orphaned_review_comments(cx);
23495
23496 if let Some(buffer) = edited_buffer {
23497 if buffer.read(cx).file().is_none() {
23498 cx.emit(EditorEvent::TitleChanged);
23499 }
23500
23501 if self.project.is_some() {
23502 let buffer_id = buffer.read(cx).remote_id();
23503 self.register_buffer(buffer_id, cx);
23504 self.update_lsp_data(Some(buffer_id), window, cx);
23505 self.refresh_inlay_hints(
23506 InlayHintRefreshReason::BufferEdited(buffer_id),
23507 cx,
23508 );
23509 }
23510 }
23511
23512 cx.emit(EditorEvent::BufferEdited);
23513 cx.emit(SearchEvent::MatchesInvalidated);
23514
23515 let Some(project) = &self.project else { return };
23516 let (telemetry, is_via_ssh) = {
23517 let project = project.read(cx);
23518 let telemetry = project.client().telemetry().clone();
23519 let is_via_ssh = project.is_via_remote_server();
23520 (telemetry, is_via_ssh)
23521 };
23522 telemetry.log_edit_event("editor", is_via_ssh);
23523 }
23524 multi_buffer::Event::ExcerptsAdded {
23525 buffer,
23526 predecessor,
23527 excerpts,
23528 } => {
23529 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23530 let buffer_id = buffer.read(cx).remote_id();
23531 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23532 && let Some(project) = &self.project
23533 {
23534 update_uncommitted_diff_for_buffer(
23535 cx.entity(),
23536 project,
23537 [buffer.clone()],
23538 self.buffer.clone(),
23539 cx,
23540 )
23541 .detach();
23542 }
23543 self.update_lsp_data(Some(buffer_id), window, cx);
23544 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23545 self.colorize_brackets(false, cx);
23546 self.refresh_selected_text_highlights(true, window, cx);
23547 cx.emit(EditorEvent::ExcerptsAdded {
23548 buffer: buffer.clone(),
23549 predecessor: *predecessor,
23550 excerpts: excerpts.clone(),
23551 });
23552 }
23553 multi_buffer::Event::ExcerptsRemoved {
23554 ids,
23555 removed_buffer_ids,
23556 } => {
23557 if let Some(inlay_hints) = &mut self.inlay_hints {
23558 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23559 }
23560 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23561 for buffer_id in removed_buffer_ids {
23562 self.registered_buffers.remove(buffer_id);
23563 }
23564 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23565 cx.emit(EditorEvent::ExcerptsRemoved {
23566 ids: ids.clone(),
23567 removed_buffer_ids: removed_buffer_ids.clone(),
23568 });
23569 }
23570 multi_buffer::Event::ExcerptsEdited {
23571 excerpt_ids,
23572 buffer_ids,
23573 } => {
23574 self.display_map.update(cx, |map, cx| {
23575 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23576 });
23577 cx.emit(EditorEvent::ExcerptsEdited {
23578 ids: excerpt_ids.clone(),
23579 });
23580 }
23581 multi_buffer::Event::ExcerptsExpanded { ids } => {
23582 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23583 self.refresh_document_highlights(cx);
23584 for id in ids {
23585 self.fetched_tree_sitter_chunks.remove(id);
23586 }
23587 self.colorize_brackets(false, cx);
23588 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23589 }
23590 multi_buffer::Event::Reparsed(buffer_id) => {
23591 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23592 self.refresh_selected_text_highlights(true, window, cx);
23593 self.colorize_brackets(true, cx);
23594 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23595
23596 cx.emit(EditorEvent::Reparsed(*buffer_id));
23597 }
23598 multi_buffer::Event::DiffHunksToggled => {
23599 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23600 }
23601 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23602 if !is_fresh_language {
23603 self.registered_buffers.remove(&buffer_id);
23604 }
23605 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23606 cx.emit(EditorEvent::Reparsed(*buffer_id));
23607 cx.notify();
23608 }
23609 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
23610 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
23611 multi_buffer::Event::FileHandleChanged
23612 | multi_buffer::Event::Reloaded
23613 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
23614 multi_buffer::Event::DiagnosticsUpdated => {
23615 self.update_diagnostics_state(window, cx);
23616 }
23617 _ => {}
23618 };
23619 }
23620
23621 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
23622 if !self.diagnostics_enabled() {
23623 return;
23624 }
23625 self.refresh_active_diagnostics(cx);
23626 self.refresh_inline_diagnostics(true, window, cx);
23627 self.scrollbar_marker_state.dirty = true;
23628 cx.notify();
23629 }
23630
23631 pub fn start_temporary_diff_override(&mut self) {
23632 self.load_diff_task.take();
23633 self.temporary_diff_override = true;
23634 }
23635
23636 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
23637 self.temporary_diff_override = false;
23638 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
23639 self.buffer.update(cx, |buffer, cx| {
23640 buffer.set_all_diff_hunks_collapsed(cx);
23641 });
23642
23643 if let Some(project) = self.project.clone() {
23644 self.load_diff_task = Some(
23645 update_uncommitted_diff_for_buffer(
23646 cx.entity(),
23647 &project,
23648 self.buffer.read(cx).all_buffers(),
23649 self.buffer.clone(),
23650 cx,
23651 )
23652 .shared(),
23653 );
23654 }
23655 }
23656
23657 fn on_display_map_changed(
23658 &mut self,
23659 _: Entity<DisplayMap>,
23660 _: &mut Window,
23661 cx: &mut Context<Self>,
23662 ) {
23663 cx.notify();
23664 }
23665
23666 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
23667 if !self.mode.is_full() {
23668 return None;
23669 }
23670
23671 let theme_settings = theme::ThemeSettings::get_global(cx);
23672 let theme = cx.theme();
23673 let accent_colors = theme.accents().clone();
23674
23675 let accent_overrides = theme_settings
23676 .theme_overrides
23677 .get(theme.name.as_ref())
23678 .map(|theme_style| &theme_style.accents)
23679 .into_iter()
23680 .flatten()
23681 .chain(
23682 theme_settings
23683 .experimental_theme_overrides
23684 .as_ref()
23685 .map(|overrides| &overrides.accents)
23686 .into_iter()
23687 .flatten(),
23688 )
23689 .flat_map(|accent| accent.0.clone().map(SharedString::from))
23690 .collect();
23691
23692 Some(AccentData {
23693 colors: accent_colors,
23694 overrides: accent_overrides,
23695 })
23696 }
23697
23698 fn fetch_applicable_language_settings(
23699 &self,
23700 cx: &App,
23701 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
23702 if !self.mode.is_full() {
23703 return HashMap::default();
23704 }
23705
23706 self.buffer().read(cx).all_buffers().into_iter().fold(
23707 HashMap::default(),
23708 |mut acc, buffer| {
23709 let buffer = buffer.read(cx);
23710 let language = buffer.language().map(|language| language.name());
23711 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
23712 let file = buffer.file();
23713 v.insert(language_settings(language, file, cx).into_owned());
23714 }
23715 acc
23716 },
23717 )
23718 }
23719
23720 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23721 let new_language_settings = self.fetch_applicable_language_settings(cx);
23722 let language_settings_changed = new_language_settings != self.applicable_language_settings;
23723 self.applicable_language_settings = new_language_settings;
23724
23725 let new_accents = self.fetch_accent_data(cx);
23726 let accents_changed = new_accents != self.accent_data;
23727 self.accent_data = new_accents;
23728
23729 if self.diagnostics_enabled() {
23730 let new_severity = EditorSettings::get_global(cx)
23731 .diagnostics_max_severity
23732 .unwrap_or(DiagnosticSeverity::Hint);
23733 self.set_max_diagnostics_severity(new_severity, cx);
23734 }
23735 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23736 self.update_edit_prediction_settings(cx);
23737 self.refresh_edit_prediction(true, false, window, cx);
23738 self.refresh_inline_values(cx);
23739 self.refresh_inlay_hints(
23740 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
23741 self.selections.newest_anchor().head(),
23742 &self.buffer.read(cx).snapshot(cx),
23743 cx,
23744 )),
23745 cx,
23746 );
23747
23748 let old_cursor_shape = self.cursor_shape;
23749 let old_show_breadcrumbs = self.show_breadcrumbs;
23750
23751 {
23752 let editor_settings = EditorSettings::get_global(cx);
23753 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
23754 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
23755 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
23756 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
23757 }
23758
23759 if old_cursor_shape != self.cursor_shape {
23760 cx.emit(EditorEvent::CursorShapeChanged);
23761 }
23762
23763 if old_show_breadcrumbs != self.show_breadcrumbs {
23764 cx.emit(EditorEvent::BreadcrumbsChanged);
23765 }
23766
23767 let project_settings = ProjectSettings::get_global(cx);
23768 self.buffer_serialization = self
23769 .should_serialize_buffer()
23770 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
23771
23772 if self.mode.is_full() {
23773 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
23774 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
23775 if self.show_inline_diagnostics != show_inline_diagnostics {
23776 self.show_inline_diagnostics = show_inline_diagnostics;
23777 self.refresh_inline_diagnostics(false, window, cx);
23778 }
23779
23780 if self.git_blame_inline_enabled != inline_blame_enabled {
23781 self.toggle_git_blame_inline_internal(false, window, cx);
23782 }
23783
23784 let minimap_settings = EditorSettings::get_global(cx).minimap;
23785 if self.minimap_visibility != MinimapVisibility::Disabled {
23786 if self.minimap_visibility.settings_visibility()
23787 != minimap_settings.minimap_enabled()
23788 {
23789 self.set_minimap_visibility(
23790 MinimapVisibility::for_mode(self.mode(), cx),
23791 window,
23792 cx,
23793 );
23794 } else if let Some(minimap_entity) = self.minimap.as_ref() {
23795 minimap_entity.update(cx, |minimap_editor, cx| {
23796 minimap_editor.update_minimap_configuration(minimap_settings, cx)
23797 })
23798 }
23799 }
23800
23801 if language_settings_changed || accents_changed {
23802 self.colorize_brackets(true, cx);
23803 }
23804
23805 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
23806 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
23807 }) {
23808 if !inlay_splice.is_empty() {
23809 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
23810 }
23811 self.refresh_colors_for_visible_range(None, window, cx);
23812 }
23813 }
23814
23815 cx.notify();
23816 }
23817
23818 pub fn set_searchable(&mut self, searchable: bool) {
23819 self.searchable = searchable;
23820 }
23821
23822 pub fn searchable(&self) -> bool {
23823 self.searchable
23824 }
23825
23826 pub fn open_excerpts_in_split(
23827 &mut self,
23828 _: &OpenExcerptsSplit,
23829 window: &mut Window,
23830 cx: &mut Context<Self>,
23831 ) {
23832 self.open_excerpts_common(None, true, window, cx)
23833 }
23834
23835 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
23836 self.open_excerpts_common(None, false, window, cx)
23837 }
23838
23839 fn open_excerpts_common(
23840 &mut self,
23841 jump_data: Option<JumpData>,
23842 split: bool,
23843 window: &mut Window,
23844 cx: &mut Context<Self>,
23845 ) {
23846 let Some(workspace) = self.workspace() else {
23847 cx.propagate();
23848 return;
23849 };
23850
23851 if self.buffer.read(cx).is_singleton() {
23852 cx.propagate();
23853 return;
23854 }
23855
23856 let mut new_selections_by_buffer = HashMap::default();
23857 match &jump_data {
23858 Some(JumpData::MultiBufferPoint {
23859 excerpt_id,
23860 position,
23861 anchor,
23862 line_offset_from_top,
23863 }) => {
23864 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
23865 if let Some(buffer) = multi_buffer_snapshot
23866 .buffer_id_for_excerpt(*excerpt_id)
23867 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
23868 {
23869 let buffer_snapshot = buffer.read(cx).snapshot();
23870 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
23871 language::ToPoint::to_point(anchor, &buffer_snapshot)
23872 } else {
23873 buffer_snapshot.clip_point(*position, Bias::Left)
23874 };
23875 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
23876 new_selections_by_buffer.insert(
23877 buffer,
23878 (
23879 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
23880 Some(*line_offset_from_top),
23881 ),
23882 );
23883 }
23884 }
23885 Some(JumpData::MultiBufferRow {
23886 row,
23887 line_offset_from_top,
23888 }) => {
23889 let point = MultiBufferPoint::new(row.0, 0);
23890 if let Some((buffer, buffer_point, _)) =
23891 self.buffer.read(cx).point_to_buffer_point(point, cx)
23892 {
23893 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
23894 new_selections_by_buffer
23895 .entry(buffer)
23896 .or_insert((Vec::new(), Some(*line_offset_from_top)))
23897 .0
23898 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
23899 }
23900 }
23901 None => {
23902 let selections = self
23903 .selections
23904 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
23905 let multi_buffer = self.buffer.read(cx);
23906 for selection in selections {
23907 for (snapshot, range, _, anchor) in multi_buffer
23908 .snapshot(cx)
23909 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
23910 {
23911 if let Some(anchor) = anchor {
23912 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
23913 else {
23914 continue;
23915 };
23916 let offset = text::ToOffset::to_offset(
23917 &anchor.text_anchor,
23918 &buffer_handle.read(cx).snapshot(),
23919 );
23920 let range = BufferOffset(offset)..BufferOffset(offset);
23921 new_selections_by_buffer
23922 .entry(buffer_handle)
23923 .or_insert((Vec::new(), None))
23924 .0
23925 .push(range)
23926 } else {
23927 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
23928 else {
23929 continue;
23930 };
23931 new_selections_by_buffer
23932 .entry(buffer_handle)
23933 .or_insert((Vec::new(), None))
23934 .0
23935 .push(range)
23936 }
23937 }
23938 }
23939 }
23940 }
23941
23942 new_selections_by_buffer
23943 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
23944
23945 if new_selections_by_buffer.is_empty() {
23946 return;
23947 }
23948
23949 // We defer the pane interaction because we ourselves are a workspace item
23950 // and activating a new item causes the pane to call a method on us reentrantly,
23951 // which panics if we're on the stack.
23952 window.defer(cx, move |window, cx| {
23953 workspace.update(cx, |workspace, cx| {
23954 let pane = if split {
23955 workspace.adjacent_pane(window, cx)
23956 } else {
23957 workspace.active_pane().clone()
23958 };
23959
23960 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
23961 let buffer_read = buffer.read(cx);
23962 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
23963 (true, project::File::from_dyn(Some(file)).is_some())
23964 } else {
23965 (false, false)
23966 };
23967
23968 // If project file is none workspace.open_project_item will fail to open the excerpt
23969 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
23970 // so we check if there's a tab match in that case first
23971 let editor = (!has_file || !is_project_file)
23972 .then(|| {
23973 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
23974 // so `workspace.open_project_item` will never find them, always opening a new editor.
23975 // Instead, we try to activate the existing editor in the pane first.
23976 let (editor, pane_item_index, pane_item_id) =
23977 pane.read(cx).items().enumerate().find_map(|(i, item)| {
23978 let editor = item.downcast::<Editor>()?;
23979 let singleton_buffer =
23980 editor.read(cx).buffer().read(cx).as_singleton()?;
23981 if singleton_buffer == buffer {
23982 Some((editor, i, item.item_id()))
23983 } else {
23984 None
23985 }
23986 })?;
23987 pane.update(cx, |pane, cx| {
23988 pane.activate_item(pane_item_index, true, true, window, cx);
23989 if !PreviewTabsSettings::get_global(cx)
23990 .enable_preview_from_multibuffer
23991 {
23992 pane.unpreview_item_if_preview(pane_item_id);
23993 }
23994 });
23995 Some(editor)
23996 })
23997 .flatten()
23998 .unwrap_or_else(|| {
23999 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24000 .enable_keep_preview_on_code_navigation;
24001 let allow_new_preview =
24002 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
24003 workspace.open_project_item::<Self>(
24004 pane.clone(),
24005 buffer,
24006 true,
24007 true,
24008 keep_old_preview,
24009 allow_new_preview,
24010 window,
24011 cx,
24012 )
24013 });
24014
24015 editor.update(cx, |editor, cx| {
24016 if has_file && !is_project_file {
24017 editor.set_read_only(true);
24018 }
24019 let autoscroll = match scroll_offset {
24020 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
24021 None => Autoscroll::newest(),
24022 };
24023 let nav_history = editor.nav_history.take();
24024 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24025 let Some((&excerpt_id, _, buffer_snapshot)) =
24026 multibuffer_snapshot.as_singleton()
24027 else {
24028 return;
24029 };
24030 editor.change_selections(
24031 SelectionEffects::scroll(autoscroll),
24032 window,
24033 cx,
24034 |s| {
24035 s.select_ranges(ranges.into_iter().map(|range| {
24036 let range = buffer_snapshot.anchor_before(range.start)
24037 ..buffer_snapshot.anchor_after(range.end);
24038 multibuffer_snapshot
24039 .anchor_range_in_excerpt(excerpt_id, range)
24040 .unwrap()
24041 }));
24042 },
24043 );
24044 editor.nav_history = nav_history;
24045 });
24046 }
24047 })
24048 });
24049 }
24050
24051 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24052 let snapshot = self.buffer.read(cx).read(cx);
24053 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
24054 Some(
24055 ranges
24056 .iter()
24057 .map(move |range| {
24058 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24059 })
24060 .collect(),
24061 )
24062 }
24063
24064 fn selection_replacement_ranges(
24065 &self,
24066 range: Range<MultiBufferOffsetUtf16>,
24067 cx: &mut App,
24068 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24069 let selections = self
24070 .selections
24071 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24072 let newest_selection = selections
24073 .iter()
24074 .max_by_key(|selection| selection.id)
24075 .unwrap();
24076 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24077 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24078 let snapshot = self.buffer.read(cx).read(cx);
24079 selections
24080 .into_iter()
24081 .map(|mut selection| {
24082 selection.start.0.0 =
24083 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24084 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24085 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24086 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24087 })
24088 .collect()
24089 }
24090
24091 fn report_editor_event(
24092 &self,
24093 reported_event: ReportEditorEvent,
24094 file_extension: Option<String>,
24095 cx: &App,
24096 ) {
24097 if cfg!(any(test, feature = "test-support")) {
24098 return;
24099 }
24100
24101 let Some(project) = &self.project else { return };
24102
24103 // If None, we are in a file without an extension
24104 let file = self
24105 .buffer
24106 .read(cx)
24107 .as_singleton()
24108 .and_then(|b| b.read(cx).file());
24109 let file_extension = file_extension.or(file
24110 .as_ref()
24111 .and_then(|file| Path::new(file.file_name(cx)).extension())
24112 .and_then(|e| e.to_str())
24113 .map(|a| a.to_string()));
24114
24115 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24116 .map(|vim_mode| vim_mode.0)
24117 .unwrap_or(false);
24118
24119 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24120 let copilot_enabled = edit_predictions_provider
24121 == language::language_settings::EditPredictionProvider::Copilot;
24122 let copilot_enabled_for_language = self
24123 .buffer
24124 .read(cx)
24125 .language_settings(cx)
24126 .show_edit_predictions;
24127
24128 let project = project.read(cx);
24129 let event_type = reported_event.event_type();
24130
24131 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24132 telemetry::event!(
24133 event_type,
24134 type = if auto_saved {"autosave"} else {"manual"},
24135 file_extension,
24136 vim_mode,
24137 copilot_enabled,
24138 copilot_enabled_for_language,
24139 edit_predictions_provider,
24140 is_via_ssh = project.is_via_remote_server(),
24141 );
24142 } else {
24143 telemetry::event!(
24144 event_type,
24145 file_extension,
24146 vim_mode,
24147 copilot_enabled,
24148 copilot_enabled_for_language,
24149 edit_predictions_provider,
24150 is_via_ssh = project.is_via_remote_server(),
24151 );
24152 };
24153 }
24154
24155 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24156 /// with each line being an array of {text, highlight} objects.
24157 fn copy_highlight_json(
24158 &mut self,
24159 _: &CopyHighlightJson,
24160 window: &mut Window,
24161 cx: &mut Context<Self>,
24162 ) {
24163 #[derive(Serialize)]
24164 struct Chunk<'a> {
24165 text: String,
24166 highlight: Option<&'a str>,
24167 }
24168
24169 let snapshot = self.buffer.read(cx).snapshot(cx);
24170 let range = self
24171 .selected_text_range(false, window, cx)
24172 .and_then(|selection| {
24173 if selection.range.is_empty() {
24174 None
24175 } else {
24176 Some(
24177 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24178 selection.range.start,
24179 )))
24180 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24181 selection.range.end,
24182 ))),
24183 )
24184 }
24185 })
24186 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24187
24188 let chunks = snapshot.chunks(range, true);
24189 let mut lines = Vec::new();
24190 let mut line: VecDeque<Chunk> = VecDeque::new();
24191
24192 let Some(style) = self.style.as_ref() else {
24193 return;
24194 };
24195
24196 for chunk in chunks {
24197 let highlight = chunk
24198 .syntax_highlight_id
24199 .and_then(|id| id.name(&style.syntax));
24200 let mut chunk_lines = chunk.text.split('\n').peekable();
24201 while let Some(text) = chunk_lines.next() {
24202 let mut merged_with_last_token = false;
24203 if let Some(last_token) = line.back_mut()
24204 && last_token.highlight == highlight
24205 {
24206 last_token.text.push_str(text);
24207 merged_with_last_token = true;
24208 }
24209
24210 if !merged_with_last_token {
24211 line.push_back(Chunk {
24212 text: text.into(),
24213 highlight,
24214 });
24215 }
24216
24217 if chunk_lines.peek().is_some() {
24218 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24219 line.pop_front();
24220 }
24221 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24222 line.pop_back();
24223 }
24224
24225 lines.push(mem::take(&mut line));
24226 }
24227 }
24228 }
24229
24230 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24231 return;
24232 };
24233 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24234 }
24235
24236 pub fn open_context_menu(
24237 &mut self,
24238 _: &OpenContextMenu,
24239 window: &mut Window,
24240 cx: &mut Context<Self>,
24241 ) {
24242 self.request_autoscroll(Autoscroll::newest(), cx);
24243 let position = self
24244 .selections
24245 .newest_display(&self.display_snapshot(cx))
24246 .start;
24247 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24248 }
24249
24250 pub fn replay_insert_event(
24251 &mut self,
24252 text: &str,
24253 relative_utf16_range: Option<Range<isize>>,
24254 window: &mut Window,
24255 cx: &mut Context<Self>,
24256 ) {
24257 if !self.input_enabled {
24258 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24259 return;
24260 }
24261 if let Some(relative_utf16_range) = relative_utf16_range {
24262 let selections = self
24263 .selections
24264 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24265 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24266 let new_ranges = selections.into_iter().map(|range| {
24267 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24268 range
24269 .head()
24270 .0
24271 .0
24272 .saturating_add_signed(relative_utf16_range.start),
24273 ));
24274 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24275 range
24276 .head()
24277 .0
24278 .0
24279 .saturating_add_signed(relative_utf16_range.end),
24280 ));
24281 start..end
24282 });
24283 s.select_ranges(new_ranges);
24284 });
24285 }
24286
24287 self.handle_input(text, window, cx);
24288 }
24289
24290 pub fn is_focused(&self, window: &Window) -> bool {
24291 self.focus_handle.is_focused(window)
24292 }
24293
24294 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24295 cx.emit(EditorEvent::Focused);
24296
24297 if let Some(descendant) = self
24298 .last_focused_descendant
24299 .take()
24300 .and_then(|descendant| descendant.upgrade())
24301 {
24302 window.focus(&descendant, cx);
24303 } else {
24304 if let Some(blame) = self.blame.as_ref() {
24305 blame.update(cx, GitBlame::focus)
24306 }
24307
24308 self.blink_manager.update(cx, BlinkManager::enable);
24309 self.show_cursor_names(window, cx);
24310 self.buffer.update(cx, |buffer, cx| {
24311 buffer.finalize_last_transaction(cx);
24312 if self.leader_id.is_none() {
24313 buffer.set_active_selections(
24314 &self.selections.disjoint_anchors_arc(),
24315 self.selections.line_mode(),
24316 self.cursor_shape,
24317 cx,
24318 );
24319 }
24320 });
24321
24322 if let Some(position_map) = self.last_position_map.clone() {
24323 EditorElement::mouse_moved(
24324 self,
24325 &MouseMoveEvent {
24326 position: window.mouse_position(),
24327 pressed_button: None,
24328 modifiers: window.modifiers(),
24329 },
24330 &position_map,
24331 window,
24332 cx,
24333 );
24334 }
24335 }
24336 }
24337
24338 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24339 cx.emit(EditorEvent::FocusedIn)
24340 }
24341
24342 fn handle_focus_out(
24343 &mut self,
24344 event: FocusOutEvent,
24345 _window: &mut Window,
24346 cx: &mut Context<Self>,
24347 ) {
24348 if event.blurred != self.focus_handle {
24349 self.last_focused_descendant = Some(event.blurred);
24350 }
24351 self.selection_drag_state = SelectionDragState::None;
24352 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24353 }
24354
24355 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24356 self.blink_manager.update(cx, BlinkManager::disable);
24357 self.buffer
24358 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24359
24360 if let Some(blame) = self.blame.as_ref() {
24361 blame.update(cx, GitBlame::blur)
24362 }
24363 if !self.hover_state.focused(window, cx) {
24364 hide_hover(self, cx);
24365 }
24366 if !self
24367 .context_menu
24368 .borrow()
24369 .as_ref()
24370 .is_some_and(|context_menu| context_menu.focused(window, cx))
24371 {
24372 self.hide_context_menu(window, cx);
24373 }
24374 self.take_active_edit_prediction(cx);
24375 cx.emit(EditorEvent::Blurred);
24376 cx.notify();
24377 }
24378
24379 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24380 let mut pending: String = window
24381 .pending_input_keystrokes()
24382 .into_iter()
24383 .flatten()
24384 .filter_map(|keystroke| keystroke.key_char.clone())
24385 .collect();
24386
24387 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24388 pending = "".to_string();
24389 }
24390
24391 let existing_pending = self
24392 .text_highlights::<PendingInput>(cx)
24393 .map(|(_, ranges)| ranges.to_vec());
24394 if existing_pending.is_none() && pending.is_empty() {
24395 return;
24396 }
24397 let transaction =
24398 self.transact(window, cx, |this, window, cx| {
24399 let selections = this
24400 .selections
24401 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24402 let edits = selections
24403 .iter()
24404 .map(|selection| (selection.end..selection.end, pending.clone()));
24405 this.edit(edits, cx);
24406 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24407 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24408 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24409 }));
24410 });
24411 if let Some(existing_ranges) = existing_pending {
24412 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24413 this.edit(edits, cx);
24414 }
24415 });
24416
24417 let snapshot = self.snapshot(window, cx);
24418 let ranges = self
24419 .selections
24420 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24421 .into_iter()
24422 .map(|selection| {
24423 snapshot.buffer_snapshot().anchor_after(selection.end)
24424 ..snapshot
24425 .buffer_snapshot()
24426 .anchor_before(selection.end + pending.len())
24427 })
24428 .collect();
24429
24430 if pending.is_empty() {
24431 self.clear_highlights::<PendingInput>(cx);
24432 } else {
24433 self.highlight_text::<PendingInput>(
24434 ranges,
24435 HighlightStyle {
24436 underline: Some(UnderlineStyle {
24437 thickness: px(1.),
24438 color: None,
24439 wavy: false,
24440 }),
24441 ..Default::default()
24442 },
24443 cx,
24444 );
24445 }
24446
24447 self.ime_transaction = self.ime_transaction.or(transaction);
24448 if let Some(transaction) = self.ime_transaction {
24449 self.buffer.update(cx, |buffer, cx| {
24450 buffer.group_until_transaction(transaction, cx);
24451 });
24452 }
24453
24454 if self.text_highlights::<PendingInput>(cx).is_none() {
24455 self.ime_transaction.take();
24456 }
24457 }
24458
24459 pub fn register_action_renderer(
24460 &mut self,
24461 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24462 ) -> Subscription {
24463 let id = self.next_editor_action_id.post_inc();
24464 self.editor_actions
24465 .borrow_mut()
24466 .insert(id, Box::new(listener));
24467
24468 let editor_actions = self.editor_actions.clone();
24469 Subscription::new(move || {
24470 editor_actions.borrow_mut().remove(&id);
24471 })
24472 }
24473
24474 pub fn register_action<A: Action>(
24475 &mut self,
24476 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24477 ) -> Subscription {
24478 let id = self.next_editor_action_id.post_inc();
24479 let listener = Arc::new(listener);
24480 self.editor_actions.borrow_mut().insert(
24481 id,
24482 Box::new(move |_, window, _| {
24483 let listener = listener.clone();
24484 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24485 let action = action.downcast_ref().unwrap();
24486 if phase == DispatchPhase::Bubble {
24487 listener(action, window, cx)
24488 }
24489 })
24490 }),
24491 );
24492
24493 let editor_actions = self.editor_actions.clone();
24494 Subscription::new(move || {
24495 editor_actions.borrow_mut().remove(&id);
24496 })
24497 }
24498
24499 pub fn file_header_size(&self) -> u32 {
24500 FILE_HEADER_HEIGHT
24501 }
24502
24503 pub fn restore(
24504 &mut self,
24505 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24506 window: &mut Window,
24507 cx: &mut Context<Self>,
24508 ) {
24509 self.buffer().update(cx, |multi_buffer, cx| {
24510 for (buffer_id, changes) in revert_changes {
24511 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24512 buffer.update(cx, |buffer, cx| {
24513 buffer.edit(
24514 changes
24515 .into_iter()
24516 .map(|(range, text)| (range, text.to_string())),
24517 None,
24518 cx,
24519 );
24520 });
24521 }
24522 }
24523 });
24524 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24525 selections.refresh()
24526 });
24527 }
24528
24529 pub fn to_pixel_point(
24530 &mut self,
24531 source: multi_buffer::Anchor,
24532 editor_snapshot: &EditorSnapshot,
24533 window: &mut Window,
24534 cx: &App,
24535 ) -> Option<gpui::Point<Pixels>> {
24536 let source_point = source.to_display_point(editor_snapshot);
24537 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
24538 }
24539
24540 pub fn display_to_pixel_point(
24541 &mut self,
24542 source: DisplayPoint,
24543 editor_snapshot: &EditorSnapshot,
24544 window: &mut Window,
24545 cx: &App,
24546 ) -> Option<gpui::Point<Pixels>> {
24547 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
24548 let text_layout_details = self.text_layout_details(window);
24549 let scroll_top = text_layout_details
24550 .scroll_anchor
24551 .scroll_position(editor_snapshot)
24552 .y;
24553
24554 if source.row().as_f64() < scroll_top.floor() {
24555 return None;
24556 }
24557 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
24558 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
24559 Some(gpui::Point::new(source_x, source_y))
24560 }
24561
24562 pub fn has_visible_completions_menu(&self) -> bool {
24563 !self.edit_prediction_preview_is_active()
24564 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
24565 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
24566 })
24567 }
24568
24569 pub fn register_addon<T: Addon>(&mut self, instance: T) {
24570 if self.mode.is_minimap() {
24571 return;
24572 }
24573 self.addons
24574 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
24575 }
24576
24577 pub fn unregister_addon<T: Addon>(&mut self) {
24578 self.addons.remove(&std::any::TypeId::of::<T>());
24579 }
24580
24581 pub fn addon<T: Addon>(&self) -> Option<&T> {
24582 let type_id = std::any::TypeId::of::<T>();
24583 self.addons
24584 .get(&type_id)
24585 .and_then(|item| item.to_any().downcast_ref::<T>())
24586 }
24587
24588 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
24589 let type_id = std::any::TypeId::of::<T>();
24590 self.addons
24591 .get_mut(&type_id)
24592 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
24593 }
24594
24595 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
24596 let text_layout_details = self.text_layout_details(window);
24597 let style = &text_layout_details.editor_style;
24598 let font_id = window.text_system().resolve_font(&style.text.font());
24599 let font_size = style.text.font_size.to_pixels(window.rem_size());
24600 let line_height = style.text.line_height_in_pixels(window.rem_size());
24601 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
24602 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
24603
24604 CharacterDimensions {
24605 em_width,
24606 em_advance,
24607 line_height,
24608 }
24609 }
24610
24611 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
24612 self.load_diff_task.clone()
24613 }
24614
24615 fn read_metadata_from_db(
24616 &mut self,
24617 item_id: u64,
24618 workspace_id: WorkspaceId,
24619 window: &mut Window,
24620 cx: &mut Context<Editor>,
24621 ) {
24622 if self.buffer_kind(cx) == ItemBufferKind::Singleton
24623 && !self.mode.is_minimap()
24624 && WorkspaceSettings::get(None, cx).restore_on_startup
24625 != RestoreOnStartupBehavior::EmptyTab
24626 {
24627 let buffer_snapshot = OnceCell::new();
24628
24629 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
24630 && !folds.is_empty()
24631 {
24632 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24633 let snapshot_len = snapshot.len().0;
24634
24635 // Helper: search for fingerprint in buffer, return offset if found
24636 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
24637 // Ensure we start at a character boundary (defensive)
24638 let search_start = snapshot
24639 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
24640 .0;
24641 let search_end = snapshot_len.saturating_sub(fingerprint.len());
24642
24643 let mut byte_offset = search_start;
24644 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
24645 if byte_offset > search_end {
24646 break;
24647 }
24648 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
24649 return Some(byte_offset);
24650 }
24651 byte_offset += ch.len_utf8();
24652 }
24653 None
24654 };
24655
24656 // Track search position to handle duplicate fingerprints correctly.
24657 // Folds are stored in document order, so we advance after each match.
24658 let mut search_start = 0usize;
24659
24660 let valid_folds: Vec<_> = folds
24661 .into_iter()
24662 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
24663 // Skip folds without fingerprints (old data before migration)
24664 let sfp = start_fp?;
24665 let efp = end_fp?;
24666 let efp_len = efp.len();
24667
24668 // Fast path: check if fingerprints match at stored offsets
24669 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
24670 let start_matches = stored_start < snapshot_len
24671 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
24672 let efp_check_pos = stored_end.saturating_sub(efp_len);
24673 let end_matches = efp_check_pos >= stored_start
24674 && stored_end <= snapshot_len
24675 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
24676
24677 let (new_start, new_end) = if start_matches && end_matches {
24678 // Offsets unchanged, use stored values
24679 (stored_start, stored_end)
24680 } else if sfp == efp {
24681 // Short fold: identical fingerprints can only match once per search
24682 // Use stored fold length to compute new_end
24683 let new_start = find_fingerprint(&sfp, search_start)?;
24684 let fold_len = stored_end - stored_start;
24685 let new_end = new_start + fold_len;
24686 (new_start, new_end)
24687 } else {
24688 // Slow path: search for fingerprints in buffer
24689 let new_start = find_fingerprint(&sfp, search_start)?;
24690 // Search for end_fp after start, then add efp_len to get actual fold end
24691 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
24692 let new_end = efp_pos + efp_len;
24693 (new_start, new_end)
24694 };
24695
24696 // Advance search position for next fold
24697 search_start = new_end;
24698
24699 // Validate fold makes sense (end must be after start)
24700 if new_end <= new_start {
24701 return None;
24702 }
24703
24704 Some(
24705 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
24706 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
24707 )
24708 })
24709 .collect();
24710
24711 if !valid_folds.is_empty() {
24712 self.fold_ranges(valid_folds, false, window, cx);
24713
24714 // Migrate folds to current entity_id before workspace cleanup runs.
24715 // Entity IDs change between sessions, but workspace cleanup deletes
24716 // old editor rows (cascading to folds) based on current entity IDs.
24717 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
24718 if new_editor_id != item_id {
24719 cx.spawn(async move |_, _| {
24720 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
24721 .await
24722 .log_err();
24723 })
24724 .detach();
24725 }
24726 }
24727 }
24728
24729 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
24730 && !selections.is_empty()
24731 {
24732 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24733 // skip adding the initial selection to selection history
24734 self.selection_history.mode = SelectionHistoryMode::Skipping;
24735 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24736 s.select_ranges(selections.into_iter().map(|(start, end)| {
24737 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
24738 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
24739 }));
24740 });
24741 self.selection_history.mode = SelectionHistoryMode::Normal;
24742 };
24743 }
24744
24745 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
24746 }
24747
24748 fn update_lsp_data(
24749 &mut self,
24750 for_buffer: Option<BufferId>,
24751 window: &mut Window,
24752 cx: &mut Context<'_, Self>,
24753 ) {
24754 self.pull_diagnostics(for_buffer, window, cx);
24755 self.refresh_colors_for_visible_range(for_buffer, window, cx);
24756 }
24757
24758 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
24759 if self.ignore_lsp_data() {
24760 return;
24761 }
24762 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
24763 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
24764 }
24765 }
24766
24767 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
24768 if self.ignore_lsp_data() {
24769 return;
24770 }
24771
24772 if !self.registered_buffers.contains_key(&buffer_id)
24773 && let Some(project) = self.project.as_ref()
24774 {
24775 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
24776 project.update(cx, |project, cx| {
24777 self.registered_buffers.insert(
24778 buffer_id,
24779 project.register_buffer_with_language_servers(&buffer, cx),
24780 );
24781 });
24782 } else {
24783 self.registered_buffers.remove(&buffer_id);
24784 }
24785 }
24786 }
24787
24788 fn ignore_lsp_data(&self) -> bool {
24789 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
24790 // skip any LSP updates for it.
24791 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
24792 }
24793
24794 fn create_style(&self, cx: &App) -> EditorStyle {
24795 let settings = ThemeSettings::get_global(cx);
24796
24797 let mut text_style = match self.mode {
24798 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24799 color: cx.theme().colors().editor_foreground,
24800 font_family: settings.ui_font.family.clone(),
24801 font_features: settings.ui_font.features.clone(),
24802 font_fallbacks: settings.ui_font.fallbacks.clone(),
24803 font_size: rems(0.875).into(),
24804 font_weight: settings.ui_font.weight,
24805 line_height: relative(settings.buffer_line_height.value()),
24806 ..Default::default()
24807 },
24808 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24809 color: cx.theme().colors().editor_foreground,
24810 font_family: settings.buffer_font.family.clone(),
24811 font_features: settings.buffer_font.features.clone(),
24812 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24813 font_size: settings.buffer_font_size(cx).into(),
24814 font_weight: settings.buffer_font.weight,
24815 line_height: relative(settings.buffer_line_height.value()),
24816 ..Default::default()
24817 },
24818 };
24819 if let Some(text_style_refinement) = &self.text_style_refinement {
24820 text_style.refine(text_style_refinement)
24821 }
24822
24823 let background = match self.mode {
24824 EditorMode::SingleLine => cx.theme().system().transparent,
24825 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24826 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24827 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24828 };
24829
24830 EditorStyle {
24831 background,
24832 border: cx.theme().colors().border,
24833 local_player: cx.theme().players().local(),
24834 text: text_style,
24835 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24836 syntax: cx.theme().syntax().clone(),
24837 status: cx.theme().status().clone(),
24838 inlay_hints_style: make_inlay_hints_style(cx),
24839 edit_prediction_styles: make_suggestion_styles(cx),
24840 unnecessary_code_fade: settings.unnecessary_code_fade,
24841 show_underlines: self.diagnostics_enabled(),
24842 }
24843 }
24844 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
24845 let cursor = self.selections.newest_anchor().head();
24846 let multibuffer = self.buffer().read(cx);
24847 let is_singleton = multibuffer.is_singleton();
24848 let (buffer_id, symbols) = multibuffer
24849 .read(cx)
24850 .symbols_containing(cursor, Some(variant.syntax()))?;
24851 let buffer = multibuffer.buffer(buffer_id)?;
24852
24853 let buffer = buffer.read(cx);
24854 let settings = ThemeSettings::get_global(cx);
24855 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
24856 let mut breadcrumbs = if is_singleton {
24857 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
24858 buffer
24859 .snapshot()
24860 .resolve_file_path(
24861 self.project
24862 .as_ref()
24863 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
24864 .unwrap_or_default(),
24865 cx,
24866 )
24867 .unwrap_or_else(|| {
24868 if multibuffer.is_singleton() {
24869 multibuffer.title(cx).to_string()
24870 } else {
24871 "untitled".to_string()
24872 }
24873 })
24874 });
24875 vec![BreadcrumbText {
24876 text,
24877 highlights: None,
24878 font: Some(settings.buffer_font.clone()),
24879 }]
24880 } else {
24881 vec![]
24882 };
24883
24884 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
24885 text: symbol.text,
24886 highlights: Some(symbol.highlight_ranges),
24887 font: Some(settings.buffer_font.clone()),
24888 }));
24889 Some(breadcrumbs)
24890 }
24891}
24892
24893fn edit_for_markdown_paste<'a>(
24894 buffer: &MultiBufferSnapshot,
24895 range: Range<MultiBufferOffset>,
24896 to_insert: &'a str,
24897 url: Option<url::Url>,
24898) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
24899 if url.is_none() {
24900 return (range, Cow::Borrowed(to_insert));
24901 };
24902
24903 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
24904
24905 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
24906 Cow::Borrowed(to_insert)
24907 } else {
24908 Cow::Owned(format!("[{old_text}]({to_insert})"))
24909 };
24910 (range, new_text)
24911}
24912
24913fn process_completion_for_edit(
24914 completion: &Completion,
24915 intent: CompletionIntent,
24916 buffer: &Entity<Buffer>,
24917 cursor_position: &text::Anchor,
24918 cx: &mut Context<Editor>,
24919) -> CompletionEdit {
24920 let buffer = buffer.read(cx);
24921 let buffer_snapshot = buffer.snapshot();
24922 let (snippet, new_text) = if completion.is_snippet() {
24923 let mut snippet_source = completion.new_text.clone();
24924 // Workaround for typescript language server issues so that methods don't expand within
24925 // strings and functions with type expressions. The previous point is used because the query
24926 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
24927 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
24928 let previous_point = if previous_point.column > 0 {
24929 cursor_position.to_previous_offset(&buffer_snapshot)
24930 } else {
24931 cursor_position.to_offset(&buffer_snapshot)
24932 };
24933 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
24934 && scope.prefers_label_for_snippet_in_completion()
24935 && let Some(label) = completion.label()
24936 && matches!(
24937 completion.kind(),
24938 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
24939 )
24940 {
24941 snippet_source = label;
24942 }
24943 match Snippet::parse(&snippet_source).log_err() {
24944 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
24945 None => (None, completion.new_text.clone()),
24946 }
24947 } else {
24948 (None, completion.new_text.clone())
24949 };
24950
24951 let mut range_to_replace = {
24952 let replace_range = &completion.replace_range;
24953 if let CompletionSource::Lsp {
24954 insert_range: Some(insert_range),
24955 ..
24956 } = &completion.source
24957 {
24958 debug_assert_eq!(
24959 insert_range.start, replace_range.start,
24960 "insert_range and replace_range should start at the same position"
24961 );
24962 debug_assert!(
24963 insert_range
24964 .start
24965 .cmp(cursor_position, &buffer_snapshot)
24966 .is_le(),
24967 "insert_range should start before or at cursor position"
24968 );
24969 debug_assert!(
24970 replace_range
24971 .start
24972 .cmp(cursor_position, &buffer_snapshot)
24973 .is_le(),
24974 "replace_range should start before or at cursor position"
24975 );
24976
24977 let should_replace = match intent {
24978 CompletionIntent::CompleteWithInsert => false,
24979 CompletionIntent::CompleteWithReplace => true,
24980 CompletionIntent::Complete | CompletionIntent::Compose => {
24981 let insert_mode =
24982 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
24983 .completions
24984 .lsp_insert_mode;
24985 match insert_mode {
24986 LspInsertMode::Insert => false,
24987 LspInsertMode::Replace => true,
24988 LspInsertMode::ReplaceSubsequence => {
24989 let mut text_to_replace = buffer.chars_for_range(
24990 buffer.anchor_before(replace_range.start)
24991 ..buffer.anchor_after(replace_range.end),
24992 );
24993 let mut current_needle = text_to_replace.next();
24994 for haystack_ch in completion.label.text.chars() {
24995 if let Some(needle_ch) = current_needle
24996 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
24997 {
24998 current_needle = text_to_replace.next();
24999 }
25000 }
25001 current_needle.is_none()
25002 }
25003 LspInsertMode::ReplaceSuffix => {
25004 if replace_range
25005 .end
25006 .cmp(cursor_position, &buffer_snapshot)
25007 .is_gt()
25008 {
25009 let range_after_cursor = *cursor_position..replace_range.end;
25010 let text_after_cursor = buffer
25011 .text_for_range(
25012 buffer.anchor_before(range_after_cursor.start)
25013 ..buffer.anchor_after(range_after_cursor.end),
25014 )
25015 .collect::<String>()
25016 .to_ascii_lowercase();
25017 completion
25018 .label
25019 .text
25020 .to_ascii_lowercase()
25021 .ends_with(&text_after_cursor)
25022 } else {
25023 true
25024 }
25025 }
25026 }
25027 }
25028 };
25029
25030 if should_replace {
25031 replace_range.clone()
25032 } else {
25033 insert_range.clone()
25034 }
25035 } else {
25036 replace_range.clone()
25037 }
25038 };
25039
25040 if range_to_replace
25041 .end
25042 .cmp(cursor_position, &buffer_snapshot)
25043 .is_lt()
25044 {
25045 range_to_replace.end = *cursor_position;
25046 }
25047
25048 let replace_range = range_to_replace.to_offset(buffer);
25049 CompletionEdit {
25050 new_text,
25051 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25052 snippet,
25053 }
25054}
25055
25056struct CompletionEdit {
25057 new_text: String,
25058 replace_range: Range<BufferOffset>,
25059 snippet: Option<Snippet>,
25060}
25061
25062fn comment_delimiter_for_newline(
25063 start_point: &Point,
25064 buffer: &MultiBufferSnapshot,
25065 language: &LanguageScope,
25066) -> Option<Arc<str>> {
25067 let delimiters = language.line_comment_prefixes();
25068 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25069 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25070
25071 let num_of_whitespaces = snapshot
25072 .chars_for_range(range.clone())
25073 .take_while(|c| c.is_whitespace())
25074 .count();
25075 let comment_candidate = snapshot
25076 .chars_for_range(range.clone())
25077 .skip(num_of_whitespaces)
25078 .take(max_len_of_delimiter)
25079 .collect::<String>();
25080 let (delimiter, trimmed_len) = delimiters
25081 .iter()
25082 .filter_map(|delimiter| {
25083 let prefix = delimiter.trim_end();
25084 if comment_candidate.starts_with(prefix) {
25085 Some((delimiter, prefix.len()))
25086 } else {
25087 None
25088 }
25089 })
25090 .max_by_key(|(_, len)| *len)?;
25091
25092 if let Some(BlockCommentConfig {
25093 start: block_start, ..
25094 }) = language.block_comment()
25095 {
25096 let block_start_trimmed = block_start.trim_end();
25097 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25098 let line_content = snapshot
25099 .chars_for_range(range)
25100 .skip(num_of_whitespaces)
25101 .take(block_start_trimmed.len())
25102 .collect::<String>();
25103
25104 if line_content.starts_with(block_start_trimmed) {
25105 return None;
25106 }
25107 }
25108 }
25109
25110 let cursor_is_placed_after_comment_marker =
25111 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25112 if cursor_is_placed_after_comment_marker {
25113 Some(delimiter.clone())
25114 } else {
25115 None
25116 }
25117}
25118
25119fn documentation_delimiter_for_newline(
25120 start_point: &Point,
25121 buffer: &MultiBufferSnapshot,
25122 language: &LanguageScope,
25123 newline_config: &mut NewlineConfig,
25124) -> Option<Arc<str>> {
25125 let BlockCommentConfig {
25126 start: start_tag,
25127 end: end_tag,
25128 prefix: delimiter,
25129 tab_size: len,
25130 } = language.documentation_comment()?;
25131 let is_within_block_comment = buffer
25132 .language_scope_at(*start_point)
25133 .is_some_and(|scope| scope.override_name() == Some("comment"));
25134 if !is_within_block_comment {
25135 return None;
25136 }
25137
25138 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25139
25140 let num_of_whitespaces = snapshot
25141 .chars_for_range(range.clone())
25142 .take_while(|c| c.is_whitespace())
25143 .count();
25144
25145 // 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.
25146 let column = start_point.column;
25147 let cursor_is_after_start_tag = {
25148 let start_tag_len = start_tag.len();
25149 let start_tag_line = snapshot
25150 .chars_for_range(range.clone())
25151 .skip(num_of_whitespaces)
25152 .take(start_tag_len)
25153 .collect::<String>();
25154 if start_tag_line.starts_with(start_tag.as_ref()) {
25155 num_of_whitespaces + start_tag_len <= column as usize
25156 } else {
25157 false
25158 }
25159 };
25160
25161 let cursor_is_after_delimiter = {
25162 let delimiter_trim = delimiter.trim_end();
25163 let delimiter_line = snapshot
25164 .chars_for_range(range.clone())
25165 .skip(num_of_whitespaces)
25166 .take(delimiter_trim.len())
25167 .collect::<String>();
25168 if delimiter_line.starts_with(delimiter_trim) {
25169 num_of_whitespaces + delimiter_trim.len() <= column as usize
25170 } else {
25171 false
25172 }
25173 };
25174
25175 let mut needs_extra_line = false;
25176 let mut extra_line_additional_indent = IndentSize::spaces(0);
25177
25178 let cursor_is_before_end_tag_if_exists = {
25179 let mut char_position = 0u32;
25180 let mut end_tag_offset = None;
25181
25182 'outer: for chunk in snapshot.text_for_range(range) {
25183 if let Some(byte_pos) = chunk.find(&**end_tag) {
25184 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25185 end_tag_offset = Some(char_position + chars_before_match);
25186 break 'outer;
25187 }
25188 char_position += chunk.chars().count() as u32;
25189 }
25190
25191 if let Some(end_tag_offset) = end_tag_offset {
25192 let cursor_is_before_end_tag = column <= end_tag_offset;
25193 if cursor_is_after_start_tag {
25194 if cursor_is_before_end_tag {
25195 needs_extra_line = true;
25196 }
25197 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25198 if cursor_is_at_start_of_end_tag {
25199 extra_line_additional_indent.len = *len;
25200 }
25201 }
25202 cursor_is_before_end_tag
25203 } else {
25204 true
25205 }
25206 };
25207
25208 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25209 && cursor_is_before_end_tag_if_exists
25210 {
25211 let additional_indent = if cursor_is_after_start_tag {
25212 IndentSize::spaces(*len)
25213 } else {
25214 IndentSize::spaces(0)
25215 };
25216
25217 *newline_config = NewlineConfig::Newline {
25218 additional_indent,
25219 extra_line_additional_indent: if needs_extra_line {
25220 Some(extra_line_additional_indent)
25221 } else {
25222 None
25223 },
25224 prevent_auto_indent: true,
25225 };
25226 Some(delimiter.clone())
25227 } else {
25228 None
25229 }
25230}
25231
25232const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25233
25234fn list_delimiter_for_newline(
25235 start_point: &Point,
25236 buffer: &MultiBufferSnapshot,
25237 language: &LanguageScope,
25238 newline_config: &mut NewlineConfig,
25239) -> Option<Arc<str>> {
25240 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25241
25242 let num_of_whitespaces = snapshot
25243 .chars_for_range(range.clone())
25244 .take_while(|c| c.is_whitespace())
25245 .count();
25246
25247 let task_list_entries: Vec<_> = language
25248 .task_list()
25249 .into_iter()
25250 .flat_map(|config| {
25251 config
25252 .prefixes
25253 .iter()
25254 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25255 })
25256 .collect();
25257 let unordered_list_entries: Vec<_> = language
25258 .unordered_list()
25259 .iter()
25260 .map(|marker| (marker.as_ref(), marker.as_ref()))
25261 .collect();
25262
25263 let all_entries: Vec<_> = task_list_entries
25264 .into_iter()
25265 .chain(unordered_list_entries)
25266 .collect();
25267
25268 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25269 let candidate: String = snapshot
25270 .chars_for_range(range.clone())
25271 .skip(num_of_whitespaces)
25272 .take(max_prefix_len)
25273 .collect();
25274
25275 if let Some((prefix, continuation)) = all_entries
25276 .iter()
25277 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25278 .max_by_key(|(prefix, _)| prefix.len())
25279 {
25280 let end_of_prefix = num_of_whitespaces + prefix.len();
25281 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25282 let has_content_after_marker = snapshot
25283 .chars_for_range(range)
25284 .skip(end_of_prefix)
25285 .any(|c| !c.is_whitespace());
25286
25287 if has_content_after_marker && cursor_is_after_prefix {
25288 return Some((*continuation).into());
25289 }
25290
25291 if start_point.column as usize == end_of_prefix {
25292 if num_of_whitespaces == 0 {
25293 *newline_config = NewlineConfig::ClearCurrentLine;
25294 } else {
25295 *newline_config = NewlineConfig::UnindentCurrentLine {
25296 continuation: (*continuation).into(),
25297 };
25298 }
25299 }
25300
25301 return None;
25302 }
25303 }
25304
25305 let candidate: String = snapshot
25306 .chars_for_range(range.clone())
25307 .skip(num_of_whitespaces)
25308 .take(ORDERED_LIST_MAX_MARKER_LEN)
25309 .collect();
25310
25311 for ordered_config in language.ordered_list() {
25312 let regex = match Regex::new(&ordered_config.pattern) {
25313 Ok(r) => r,
25314 Err(_) => continue,
25315 };
25316
25317 if let Some(captures) = regex.captures(&candidate) {
25318 let full_match = captures.get(0)?;
25319 let marker_len = full_match.len();
25320 let end_of_prefix = num_of_whitespaces + marker_len;
25321 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25322
25323 let has_content_after_marker = snapshot
25324 .chars_for_range(range)
25325 .skip(end_of_prefix)
25326 .any(|c| !c.is_whitespace());
25327
25328 if has_content_after_marker && cursor_is_after_prefix {
25329 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25330 let continuation = ordered_config
25331 .format
25332 .replace("{1}", &(number + 1).to_string());
25333 return Some(continuation.into());
25334 }
25335
25336 if start_point.column as usize == end_of_prefix {
25337 let continuation = ordered_config.format.replace("{1}", "1");
25338 if num_of_whitespaces == 0 {
25339 *newline_config = NewlineConfig::ClearCurrentLine;
25340 } else {
25341 *newline_config = NewlineConfig::UnindentCurrentLine {
25342 continuation: continuation.into(),
25343 };
25344 }
25345 }
25346
25347 return None;
25348 }
25349 }
25350
25351 None
25352}
25353
25354fn is_list_prefix_row(
25355 row: MultiBufferRow,
25356 buffer: &MultiBufferSnapshot,
25357 language: &LanguageScope,
25358) -> bool {
25359 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25360 return false;
25361 };
25362
25363 let num_of_whitespaces = snapshot
25364 .chars_for_range(range.clone())
25365 .take_while(|c| c.is_whitespace())
25366 .count();
25367
25368 let task_list_prefixes: Vec<_> = language
25369 .task_list()
25370 .into_iter()
25371 .flat_map(|config| {
25372 config
25373 .prefixes
25374 .iter()
25375 .map(|p| p.as_ref())
25376 .collect::<Vec<_>>()
25377 })
25378 .collect();
25379 let unordered_list_markers: Vec<_> = language
25380 .unordered_list()
25381 .iter()
25382 .map(|marker| marker.as_ref())
25383 .collect();
25384 let all_prefixes: Vec<_> = task_list_prefixes
25385 .into_iter()
25386 .chain(unordered_list_markers)
25387 .collect();
25388 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25389 let candidate: String = snapshot
25390 .chars_for_range(range.clone())
25391 .skip(num_of_whitespaces)
25392 .take(max_prefix_len)
25393 .collect();
25394 if all_prefixes
25395 .iter()
25396 .any(|prefix| candidate.starts_with(*prefix))
25397 {
25398 return true;
25399 }
25400 }
25401
25402 let ordered_list_candidate: String = snapshot
25403 .chars_for_range(range)
25404 .skip(num_of_whitespaces)
25405 .take(ORDERED_LIST_MAX_MARKER_LEN)
25406 .collect();
25407 for ordered_config in language.ordered_list() {
25408 let regex = match Regex::new(&ordered_config.pattern) {
25409 Ok(r) => r,
25410 Err(_) => continue,
25411 };
25412 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25413 return captures.get(0).is_some();
25414 }
25415 }
25416
25417 false
25418}
25419
25420#[derive(Debug)]
25421enum NewlineConfig {
25422 /// Insert newline with optional additional indent and optional extra blank line
25423 Newline {
25424 additional_indent: IndentSize,
25425 extra_line_additional_indent: Option<IndentSize>,
25426 prevent_auto_indent: bool,
25427 },
25428 /// Clear the current line
25429 ClearCurrentLine,
25430 /// Unindent the current line and add continuation
25431 UnindentCurrentLine { continuation: Arc<str> },
25432}
25433
25434impl NewlineConfig {
25435 fn has_extra_line(&self) -> bool {
25436 matches!(
25437 self,
25438 Self::Newline {
25439 extra_line_additional_indent: Some(_),
25440 ..
25441 }
25442 )
25443 }
25444
25445 fn insert_extra_newline_brackets(
25446 buffer: &MultiBufferSnapshot,
25447 range: Range<MultiBufferOffset>,
25448 language: &language::LanguageScope,
25449 ) -> bool {
25450 let leading_whitespace_len = buffer
25451 .reversed_chars_at(range.start)
25452 .take_while(|c| c.is_whitespace() && *c != '\n')
25453 .map(|c| c.len_utf8())
25454 .sum::<usize>();
25455 let trailing_whitespace_len = buffer
25456 .chars_at(range.end)
25457 .take_while(|c| c.is_whitespace() && *c != '\n')
25458 .map(|c| c.len_utf8())
25459 .sum::<usize>();
25460 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25461
25462 language.brackets().any(|(pair, enabled)| {
25463 let pair_start = pair.start.trim_end();
25464 let pair_end = pair.end.trim_start();
25465
25466 enabled
25467 && pair.newline
25468 && buffer.contains_str_at(range.end, pair_end)
25469 && buffer.contains_str_at(
25470 range.start.saturating_sub_usize(pair_start.len()),
25471 pair_start,
25472 )
25473 })
25474 }
25475
25476 fn insert_extra_newline_tree_sitter(
25477 buffer: &MultiBufferSnapshot,
25478 range: Range<MultiBufferOffset>,
25479 ) -> bool {
25480 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
25481 [(buffer, range, _)] => (*buffer, range.clone()),
25482 _ => return false,
25483 };
25484 let pair = {
25485 let mut result: Option<BracketMatch<usize>> = None;
25486
25487 for pair in buffer
25488 .all_bracket_ranges(range.start.0..range.end.0)
25489 .filter(move |pair| {
25490 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
25491 })
25492 {
25493 let len = pair.close_range.end - pair.open_range.start;
25494
25495 if let Some(existing) = &result {
25496 let existing_len = existing.close_range.end - existing.open_range.start;
25497 if len > existing_len {
25498 continue;
25499 }
25500 }
25501
25502 result = Some(pair);
25503 }
25504
25505 result
25506 };
25507 let Some(pair) = pair else {
25508 return false;
25509 };
25510 pair.newline_only
25511 && buffer
25512 .chars_for_range(pair.open_range.end..range.start.0)
25513 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
25514 .all(|c| c.is_whitespace() && c != '\n')
25515 }
25516}
25517
25518fn update_uncommitted_diff_for_buffer(
25519 editor: Entity<Editor>,
25520 project: &Entity<Project>,
25521 buffers: impl IntoIterator<Item = Entity<Buffer>>,
25522 buffer: Entity<MultiBuffer>,
25523 cx: &mut App,
25524) -> Task<()> {
25525 let mut tasks = Vec::new();
25526 project.update(cx, |project, cx| {
25527 for buffer in buffers {
25528 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
25529 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
25530 }
25531 }
25532 });
25533 cx.spawn(async move |cx| {
25534 let diffs = future::join_all(tasks).await;
25535 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
25536 return;
25537 }
25538
25539 buffer.update(cx, |buffer, cx| {
25540 for diff in diffs.into_iter().flatten() {
25541 buffer.add_diff(diff, cx);
25542 }
25543 });
25544 })
25545}
25546
25547fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
25548 let tab_size = tab_size.get() as usize;
25549 let mut width = offset;
25550
25551 for ch in text.chars() {
25552 width += if ch == '\t' {
25553 tab_size - (width % tab_size)
25554 } else {
25555 1
25556 };
25557 }
25558
25559 width - offset
25560}
25561
25562#[cfg(test)]
25563mod tests {
25564 use super::*;
25565
25566 #[test]
25567 fn test_string_size_with_expanded_tabs() {
25568 let nz = |val| NonZeroU32::new(val).unwrap();
25569 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
25570 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
25571 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
25572 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
25573 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
25574 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
25575 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
25576 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
25577 }
25578}
25579
25580/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
25581struct WordBreakingTokenizer<'a> {
25582 input: &'a str,
25583}
25584
25585impl<'a> WordBreakingTokenizer<'a> {
25586 fn new(input: &'a str) -> Self {
25587 Self { input }
25588 }
25589}
25590
25591fn is_char_ideographic(ch: char) -> bool {
25592 use unicode_script::Script::*;
25593 use unicode_script::UnicodeScript;
25594 matches!(ch.script(), Han | Tangut | Yi)
25595}
25596
25597fn is_grapheme_ideographic(text: &str) -> bool {
25598 text.chars().any(is_char_ideographic)
25599}
25600
25601fn is_grapheme_whitespace(text: &str) -> bool {
25602 text.chars().any(|x| x.is_whitespace())
25603}
25604
25605fn should_stay_with_preceding_ideograph(text: &str) -> bool {
25606 text.chars()
25607 .next()
25608 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
25609}
25610
25611#[derive(PartialEq, Eq, Debug, Clone, Copy)]
25612enum WordBreakToken<'a> {
25613 Word { token: &'a str, grapheme_len: usize },
25614 InlineWhitespace { token: &'a str, grapheme_len: usize },
25615 Newline,
25616}
25617
25618impl<'a> Iterator for WordBreakingTokenizer<'a> {
25619 /// Yields a span, the count of graphemes in the token, and whether it was
25620 /// whitespace. Note that it also breaks at word boundaries.
25621 type Item = WordBreakToken<'a>;
25622
25623 fn next(&mut self) -> Option<Self::Item> {
25624 use unicode_segmentation::UnicodeSegmentation;
25625 if self.input.is_empty() {
25626 return None;
25627 }
25628
25629 let mut iter = self.input.graphemes(true).peekable();
25630 let mut offset = 0;
25631 let mut grapheme_len = 0;
25632 if let Some(first_grapheme) = iter.next() {
25633 let is_newline = first_grapheme == "\n";
25634 let is_whitespace = is_grapheme_whitespace(first_grapheme);
25635 offset += first_grapheme.len();
25636 grapheme_len += 1;
25637 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
25638 if let Some(grapheme) = iter.peek().copied()
25639 && should_stay_with_preceding_ideograph(grapheme)
25640 {
25641 offset += grapheme.len();
25642 grapheme_len += 1;
25643 }
25644 } else {
25645 let mut words = self.input[offset..].split_word_bound_indices().peekable();
25646 let mut next_word_bound = words.peek().copied();
25647 if next_word_bound.is_some_and(|(i, _)| i == 0) {
25648 next_word_bound = words.next();
25649 }
25650 while let Some(grapheme) = iter.peek().copied() {
25651 if next_word_bound.is_some_and(|(i, _)| i == offset) {
25652 break;
25653 };
25654 if is_grapheme_whitespace(grapheme) != is_whitespace
25655 || (grapheme == "\n") != is_newline
25656 {
25657 break;
25658 };
25659 offset += grapheme.len();
25660 grapheme_len += 1;
25661 iter.next();
25662 }
25663 }
25664 let token = &self.input[..offset];
25665 self.input = &self.input[offset..];
25666 if token == "\n" {
25667 Some(WordBreakToken::Newline)
25668 } else if is_whitespace {
25669 Some(WordBreakToken::InlineWhitespace {
25670 token,
25671 grapheme_len,
25672 })
25673 } else {
25674 Some(WordBreakToken::Word {
25675 token,
25676 grapheme_len,
25677 })
25678 }
25679 } else {
25680 None
25681 }
25682 }
25683}
25684
25685#[test]
25686fn test_word_breaking_tokenizer() {
25687 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
25688 ("", &[]),
25689 (" ", &[whitespace(" ", 2)]),
25690 ("Ʒ", &[word("Ʒ", 1)]),
25691 ("Ǽ", &[word("Ǽ", 1)]),
25692 ("⋑", &[word("⋑", 1)]),
25693 ("⋑⋑", &[word("⋑⋑", 2)]),
25694 (
25695 "原理,进而",
25696 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
25697 ),
25698 (
25699 "hello world",
25700 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
25701 ),
25702 (
25703 "hello, world",
25704 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
25705 ),
25706 (
25707 " hello world",
25708 &[
25709 whitespace(" ", 2),
25710 word("hello", 5),
25711 whitespace(" ", 1),
25712 word("world", 5),
25713 ],
25714 ),
25715 (
25716 "这是什么 \n 钢笔",
25717 &[
25718 word("这", 1),
25719 word("是", 1),
25720 word("什", 1),
25721 word("么", 1),
25722 whitespace(" ", 1),
25723 newline(),
25724 whitespace(" ", 1),
25725 word("钢", 1),
25726 word("笔", 1),
25727 ],
25728 ),
25729 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
25730 ];
25731
25732 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
25733 WordBreakToken::Word {
25734 token,
25735 grapheme_len,
25736 }
25737 }
25738
25739 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
25740 WordBreakToken::InlineWhitespace {
25741 token,
25742 grapheme_len,
25743 }
25744 }
25745
25746 fn newline() -> WordBreakToken<'static> {
25747 WordBreakToken::Newline
25748 }
25749
25750 for (input, result) in tests {
25751 assert_eq!(
25752 WordBreakingTokenizer::new(input)
25753 .collect::<Vec<_>>()
25754 .as_slice(),
25755 *result,
25756 );
25757 }
25758}
25759
25760fn wrap_with_prefix(
25761 first_line_prefix: String,
25762 subsequent_lines_prefix: String,
25763 unwrapped_text: String,
25764 wrap_column: usize,
25765 tab_size: NonZeroU32,
25766 preserve_existing_whitespace: bool,
25767) -> String {
25768 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
25769 let subsequent_lines_prefix_len =
25770 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
25771 let mut wrapped_text = String::new();
25772 let mut current_line = first_line_prefix;
25773 let mut is_first_line = true;
25774
25775 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
25776 let mut current_line_len = first_line_prefix_len;
25777 let mut in_whitespace = false;
25778 for token in tokenizer {
25779 let have_preceding_whitespace = in_whitespace;
25780 match token {
25781 WordBreakToken::Word {
25782 token,
25783 grapheme_len,
25784 } => {
25785 in_whitespace = false;
25786 let current_prefix_len = if is_first_line {
25787 first_line_prefix_len
25788 } else {
25789 subsequent_lines_prefix_len
25790 };
25791 if current_line_len + grapheme_len > wrap_column
25792 && current_line_len != current_prefix_len
25793 {
25794 wrapped_text.push_str(current_line.trim_end());
25795 wrapped_text.push('\n');
25796 is_first_line = false;
25797 current_line = subsequent_lines_prefix.clone();
25798 current_line_len = subsequent_lines_prefix_len;
25799 }
25800 current_line.push_str(token);
25801 current_line_len += grapheme_len;
25802 }
25803 WordBreakToken::InlineWhitespace {
25804 mut token,
25805 mut grapheme_len,
25806 } => {
25807 in_whitespace = true;
25808 if have_preceding_whitespace && !preserve_existing_whitespace {
25809 continue;
25810 }
25811 if !preserve_existing_whitespace {
25812 // Keep a single whitespace grapheme as-is
25813 if let Some(first) =
25814 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
25815 {
25816 token = first;
25817 } else {
25818 token = " ";
25819 }
25820 grapheme_len = 1;
25821 }
25822 let current_prefix_len = if is_first_line {
25823 first_line_prefix_len
25824 } else {
25825 subsequent_lines_prefix_len
25826 };
25827 if current_line_len + grapheme_len > wrap_column {
25828 wrapped_text.push_str(current_line.trim_end());
25829 wrapped_text.push('\n');
25830 is_first_line = false;
25831 current_line = subsequent_lines_prefix.clone();
25832 current_line_len = subsequent_lines_prefix_len;
25833 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
25834 current_line.push_str(token);
25835 current_line_len += grapheme_len;
25836 }
25837 }
25838 WordBreakToken::Newline => {
25839 in_whitespace = true;
25840 let current_prefix_len = if is_first_line {
25841 first_line_prefix_len
25842 } else {
25843 subsequent_lines_prefix_len
25844 };
25845 if preserve_existing_whitespace {
25846 wrapped_text.push_str(current_line.trim_end());
25847 wrapped_text.push('\n');
25848 is_first_line = false;
25849 current_line = subsequent_lines_prefix.clone();
25850 current_line_len = subsequent_lines_prefix_len;
25851 } else if have_preceding_whitespace {
25852 continue;
25853 } else if current_line_len + 1 > wrap_column
25854 && current_line_len != current_prefix_len
25855 {
25856 wrapped_text.push_str(current_line.trim_end());
25857 wrapped_text.push('\n');
25858 is_first_line = false;
25859 current_line = subsequent_lines_prefix.clone();
25860 current_line_len = subsequent_lines_prefix_len;
25861 } else if current_line_len != current_prefix_len {
25862 current_line.push(' ');
25863 current_line_len += 1;
25864 }
25865 }
25866 }
25867 }
25868
25869 if !current_line.is_empty() {
25870 wrapped_text.push_str(¤t_line);
25871 }
25872 wrapped_text
25873}
25874
25875#[test]
25876fn test_wrap_with_prefix() {
25877 assert_eq!(
25878 wrap_with_prefix(
25879 "# ".to_string(),
25880 "# ".to_string(),
25881 "abcdefg".to_string(),
25882 4,
25883 NonZeroU32::new(4).unwrap(),
25884 false,
25885 ),
25886 "# abcdefg"
25887 );
25888 assert_eq!(
25889 wrap_with_prefix(
25890 "".to_string(),
25891 "".to_string(),
25892 "\thello world".to_string(),
25893 8,
25894 NonZeroU32::new(4).unwrap(),
25895 false,
25896 ),
25897 "hello\nworld"
25898 );
25899 assert_eq!(
25900 wrap_with_prefix(
25901 "// ".to_string(),
25902 "// ".to_string(),
25903 "xx \nyy zz aa bb cc".to_string(),
25904 12,
25905 NonZeroU32::new(4).unwrap(),
25906 false,
25907 ),
25908 "// xx yy zz\n// aa bb cc"
25909 );
25910 assert_eq!(
25911 wrap_with_prefix(
25912 String::new(),
25913 String::new(),
25914 "这是什么 \n 钢笔".to_string(),
25915 3,
25916 NonZeroU32::new(4).unwrap(),
25917 false,
25918 ),
25919 "这是什\n么 钢\n笔"
25920 );
25921 assert_eq!(
25922 wrap_with_prefix(
25923 String::new(),
25924 String::new(),
25925 format!("foo{}bar", '\u{2009}'), // thin space
25926 80,
25927 NonZeroU32::new(4).unwrap(),
25928 false,
25929 ),
25930 format!("foo{}bar", '\u{2009}')
25931 );
25932}
25933
25934pub trait CollaborationHub {
25935 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
25936 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
25937 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
25938}
25939
25940impl CollaborationHub for Entity<Project> {
25941 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
25942 self.read(cx).collaborators()
25943 }
25944
25945 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
25946 self.read(cx).user_store().read(cx).participant_indices()
25947 }
25948
25949 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
25950 let this = self.read(cx);
25951 let user_ids = this.collaborators().values().map(|c| c.user_id);
25952 this.user_store().read(cx).participant_names(user_ids, cx)
25953 }
25954}
25955
25956pub trait SemanticsProvider {
25957 fn hover(
25958 &self,
25959 buffer: &Entity<Buffer>,
25960 position: text::Anchor,
25961 cx: &mut App,
25962 ) -> Option<Task<Option<Vec<project::Hover>>>>;
25963
25964 fn inline_values(
25965 &self,
25966 buffer_handle: Entity<Buffer>,
25967 range: Range<text::Anchor>,
25968 cx: &mut App,
25969 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
25970
25971 fn applicable_inlay_chunks(
25972 &self,
25973 buffer: &Entity<Buffer>,
25974 ranges: &[Range<text::Anchor>],
25975 cx: &mut App,
25976 ) -> Vec<Range<BufferRow>>;
25977
25978 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
25979
25980 fn inlay_hints(
25981 &self,
25982 invalidate: InvalidationStrategy,
25983 buffer: Entity<Buffer>,
25984 ranges: Vec<Range<text::Anchor>>,
25985 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
25986 cx: &mut App,
25987 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
25988
25989 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
25990
25991 fn document_highlights(
25992 &self,
25993 buffer: &Entity<Buffer>,
25994 position: text::Anchor,
25995 cx: &mut App,
25996 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
25997
25998 fn definitions(
25999 &self,
26000 buffer: &Entity<Buffer>,
26001 position: text::Anchor,
26002 kind: GotoDefinitionKind,
26003 cx: &mut App,
26004 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26005
26006 fn range_for_rename(
26007 &self,
26008 buffer: &Entity<Buffer>,
26009 position: text::Anchor,
26010 cx: &mut App,
26011 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26012
26013 fn perform_rename(
26014 &self,
26015 buffer: &Entity<Buffer>,
26016 position: text::Anchor,
26017 new_name: String,
26018 cx: &mut App,
26019 ) -> Option<Task<Result<ProjectTransaction>>>;
26020}
26021
26022pub trait CompletionProvider {
26023 fn completions(
26024 &self,
26025 excerpt_id: ExcerptId,
26026 buffer: &Entity<Buffer>,
26027 buffer_position: text::Anchor,
26028 trigger: CompletionContext,
26029 window: &mut Window,
26030 cx: &mut Context<Editor>,
26031 ) -> Task<Result<Vec<CompletionResponse>>>;
26032
26033 fn resolve_completions(
26034 &self,
26035 _buffer: Entity<Buffer>,
26036 _completion_indices: Vec<usize>,
26037 _completions: Rc<RefCell<Box<[Completion]>>>,
26038 _cx: &mut Context<Editor>,
26039 ) -> Task<Result<bool>> {
26040 Task::ready(Ok(false))
26041 }
26042
26043 fn apply_additional_edits_for_completion(
26044 &self,
26045 _buffer: Entity<Buffer>,
26046 _completions: Rc<RefCell<Box<[Completion]>>>,
26047 _completion_index: usize,
26048 _push_to_history: bool,
26049 _cx: &mut Context<Editor>,
26050 ) -> Task<Result<Option<language::Transaction>>> {
26051 Task::ready(Ok(None))
26052 }
26053
26054 fn is_completion_trigger(
26055 &self,
26056 buffer: &Entity<Buffer>,
26057 position: language::Anchor,
26058 text: &str,
26059 trigger_in_words: bool,
26060 cx: &mut Context<Editor>,
26061 ) -> bool;
26062
26063 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26064
26065 fn sort_completions(&self) -> bool {
26066 true
26067 }
26068
26069 fn filter_completions(&self) -> bool {
26070 true
26071 }
26072
26073 fn show_snippets(&self) -> bool {
26074 false
26075 }
26076}
26077
26078pub trait CodeActionProvider {
26079 fn id(&self) -> Arc<str>;
26080
26081 fn code_actions(
26082 &self,
26083 buffer: &Entity<Buffer>,
26084 range: Range<text::Anchor>,
26085 window: &mut Window,
26086 cx: &mut App,
26087 ) -> Task<Result<Vec<CodeAction>>>;
26088
26089 fn apply_code_action(
26090 &self,
26091 buffer_handle: Entity<Buffer>,
26092 action: CodeAction,
26093 excerpt_id: ExcerptId,
26094 push_to_history: bool,
26095 window: &mut Window,
26096 cx: &mut App,
26097 ) -> Task<Result<ProjectTransaction>>;
26098}
26099
26100impl CodeActionProvider for Entity<Project> {
26101 fn id(&self) -> Arc<str> {
26102 "project".into()
26103 }
26104
26105 fn code_actions(
26106 &self,
26107 buffer: &Entity<Buffer>,
26108 range: Range<text::Anchor>,
26109 _window: &mut Window,
26110 cx: &mut App,
26111 ) -> Task<Result<Vec<CodeAction>>> {
26112 self.update(cx, |project, cx| {
26113 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26114 let code_actions = project.code_actions(buffer, range, None, cx);
26115 cx.background_spawn(async move {
26116 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26117 Ok(code_lens_actions
26118 .context("code lens fetch")?
26119 .into_iter()
26120 .flatten()
26121 .chain(
26122 code_actions
26123 .context("code action fetch")?
26124 .into_iter()
26125 .flatten(),
26126 )
26127 .collect())
26128 })
26129 })
26130 }
26131
26132 fn apply_code_action(
26133 &self,
26134 buffer_handle: Entity<Buffer>,
26135 action: CodeAction,
26136 _excerpt_id: ExcerptId,
26137 push_to_history: bool,
26138 _window: &mut Window,
26139 cx: &mut App,
26140 ) -> Task<Result<ProjectTransaction>> {
26141 self.update(cx, |project, cx| {
26142 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26143 })
26144 }
26145}
26146
26147fn snippet_completions(
26148 project: &Project,
26149 buffer: &Entity<Buffer>,
26150 buffer_anchor: text::Anchor,
26151 classifier: CharClassifier,
26152 cx: &mut App,
26153) -> Task<Result<CompletionResponse>> {
26154 let languages = buffer.read(cx).languages_at(buffer_anchor);
26155 let snippet_store = project.snippets().read(cx);
26156
26157 let scopes: Vec<_> = languages
26158 .iter()
26159 .filter_map(|language| {
26160 let language_name = language.lsp_id();
26161 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26162
26163 if snippets.is_empty() {
26164 None
26165 } else {
26166 Some((language.default_scope(), snippets))
26167 }
26168 })
26169 .collect();
26170
26171 if scopes.is_empty() {
26172 return Task::ready(Ok(CompletionResponse {
26173 completions: vec![],
26174 display_options: CompletionDisplayOptions::default(),
26175 is_incomplete: false,
26176 }));
26177 }
26178
26179 let snapshot = buffer.read(cx).text_snapshot();
26180 let executor = cx.background_executor().clone();
26181
26182 cx.background_spawn(async move {
26183 let is_word_char = |c| classifier.is_word(c);
26184
26185 let mut is_incomplete = false;
26186 let mut completions: Vec<Completion> = Vec::new();
26187
26188 const MAX_PREFIX_LEN: usize = 128;
26189 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26190 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26191 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26192
26193 let max_buffer_window: String = snapshot
26194 .text_for_range(window_start..buffer_offset)
26195 .collect();
26196
26197 if max_buffer_window.is_empty() {
26198 return Ok(CompletionResponse {
26199 completions: vec![],
26200 display_options: CompletionDisplayOptions::default(),
26201 is_incomplete: true,
26202 });
26203 }
26204
26205 for (_scope, snippets) in scopes.into_iter() {
26206 // Sort snippets by word count to match longer snippet prefixes first.
26207 let mut sorted_snippet_candidates = snippets
26208 .iter()
26209 .enumerate()
26210 .flat_map(|(snippet_ix, snippet)| {
26211 snippet
26212 .prefix
26213 .iter()
26214 .enumerate()
26215 .map(move |(prefix_ix, prefix)| {
26216 let word_count =
26217 snippet_candidate_suffixes(prefix, is_word_char).count();
26218 ((snippet_ix, prefix_ix), prefix, word_count)
26219 })
26220 })
26221 .collect_vec();
26222 sorted_snippet_candidates
26223 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26224
26225 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26226
26227 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26228 .take(
26229 sorted_snippet_candidates
26230 .first()
26231 .map(|(_, _, word_count)| *word_count)
26232 .unwrap_or_default(),
26233 )
26234 .collect_vec();
26235
26236 const MAX_RESULTS: usize = 100;
26237 // Each match also remembers how many characters from the buffer it consumed
26238 let mut matches: Vec<(StringMatch, usize)> = vec![];
26239
26240 let mut snippet_list_cutoff_index = 0;
26241 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26242 let word_count = buffer_index + 1;
26243 // Increase `snippet_list_cutoff_index` until we have all of the
26244 // snippets with sufficiently many words.
26245 while sorted_snippet_candidates
26246 .get(snippet_list_cutoff_index)
26247 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26248 *snippet_word_count >= word_count
26249 })
26250 {
26251 snippet_list_cutoff_index += 1;
26252 }
26253
26254 // Take only the candidates with at least `word_count` many words
26255 let snippet_candidates_at_word_len =
26256 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26257
26258 let candidates = snippet_candidates_at_word_len
26259 .iter()
26260 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26261 .enumerate() // index in `sorted_snippet_candidates`
26262 // First char must match
26263 .filter(|(_ix, prefix)| {
26264 itertools::equal(
26265 prefix
26266 .chars()
26267 .next()
26268 .into_iter()
26269 .flat_map(|c| c.to_lowercase()),
26270 buffer_window
26271 .chars()
26272 .next()
26273 .into_iter()
26274 .flat_map(|c| c.to_lowercase()),
26275 )
26276 })
26277 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26278 .collect::<Vec<StringMatchCandidate>>();
26279
26280 matches.extend(
26281 fuzzy::match_strings(
26282 &candidates,
26283 &buffer_window,
26284 buffer_window.chars().any(|c| c.is_uppercase()),
26285 true,
26286 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26287 &Default::default(),
26288 executor.clone(),
26289 )
26290 .await
26291 .into_iter()
26292 .map(|string_match| (string_match, buffer_window.len())),
26293 );
26294
26295 if matches.len() >= MAX_RESULTS {
26296 break;
26297 }
26298 }
26299
26300 let to_lsp = |point: &text::Anchor| {
26301 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26302 point_to_lsp(end)
26303 };
26304 let lsp_end = to_lsp(&buffer_anchor);
26305
26306 if matches.len() >= MAX_RESULTS {
26307 is_incomplete = true;
26308 }
26309
26310 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26311 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26312 sorted_snippet_candidates[string_match.candidate_id];
26313 let snippet = &snippets[snippet_index];
26314 let start = buffer_offset - buffer_window_len;
26315 let start = snapshot.anchor_before(start);
26316 let range = start..buffer_anchor;
26317 let lsp_start = to_lsp(&start);
26318 let lsp_range = lsp::Range {
26319 start: lsp_start,
26320 end: lsp_end,
26321 };
26322 Completion {
26323 replace_range: range,
26324 new_text: snippet.body.clone(),
26325 source: CompletionSource::Lsp {
26326 insert_range: None,
26327 server_id: LanguageServerId(usize::MAX),
26328 resolved: true,
26329 lsp_completion: Box::new(lsp::CompletionItem {
26330 label: snippet.prefix.first().unwrap().clone(),
26331 kind: Some(CompletionItemKind::SNIPPET),
26332 label_details: snippet.description.as_ref().map(|description| {
26333 lsp::CompletionItemLabelDetails {
26334 detail: Some(description.clone()),
26335 description: None,
26336 }
26337 }),
26338 insert_text_format: Some(InsertTextFormat::SNIPPET),
26339 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26340 lsp::InsertReplaceEdit {
26341 new_text: snippet.body.clone(),
26342 insert: lsp_range,
26343 replace: lsp_range,
26344 },
26345 )),
26346 filter_text: Some(snippet.body.clone()),
26347 sort_text: Some(char::MAX.to_string()),
26348 ..lsp::CompletionItem::default()
26349 }),
26350 lsp_defaults: None,
26351 },
26352 label: CodeLabel {
26353 text: matching_prefix.clone(),
26354 runs: Vec::new(),
26355 filter_range: 0..matching_prefix.len(),
26356 },
26357 icon_path: None,
26358 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26359 single_line: snippet.name.clone().into(),
26360 plain_text: snippet
26361 .description
26362 .clone()
26363 .map(|description| description.into()),
26364 }),
26365 insert_text_mode: None,
26366 confirm: None,
26367 match_start: Some(start),
26368 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26369 }
26370 }));
26371 }
26372
26373 Ok(CompletionResponse {
26374 completions,
26375 display_options: CompletionDisplayOptions::default(),
26376 is_incomplete,
26377 })
26378 })
26379}
26380
26381impl CompletionProvider for Entity<Project> {
26382 fn completions(
26383 &self,
26384 _excerpt_id: ExcerptId,
26385 buffer: &Entity<Buffer>,
26386 buffer_position: text::Anchor,
26387 options: CompletionContext,
26388 _window: &mut Window,
26389 cx: &mut Context<Editor>,
26390 ) -> Task<Result<Vec<CompletionResponse>>> {
26391 self.update(cx, |project, cx| {
26392 let task = project.completions(buffer, buffer_position, options, cx);
26393 cx.background_spawn(task)
26394 })
26395 }
26396
26397 fn resolve_completions(
26398 &self,
26399 buffer: Entity<Buffer>,
26400 completion_indices: Vec<usize>,
26401 completions: Rc<RefCell<Box<[Completion]>>>,
26402 cx: &mut Context<Editor>,
26403 ) -> Task<Result<bool>> {
26404 self.update(cx, |project, cx| {
26405 project.lsp_store().update(cx, |lsp_store, cx| {
26406 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26407 })
26408 })
26409 }
26410
26411 fn apply_additional_edits_for_completion(
26412 &self,
26413 buffer: Entity<Buffer>,
26414 completions: Rc<RefCell<Box<[Completion]>>>,
26415 completion_index: usize,
26416 push_to_history: bool,
26417 cx: &mut Context<Editor>,
26418 ) -> Task<Result<Option<language::Transaction>>> {
26419 self.update(cx, |project, cx| {
26420 project.lsp_store().update(cx, |lsp_store, cx| {
26421 lsp_store.apply_additional_edits_for_completion(
26422 buffer,
26423 completions,
26424 completion_index,
26425 push_to_history,
26426 cx,
26427 )
26428 })
26429 })
26430 }
26431
26432 fn is_completion_trigger(
26433 &self,
26434 buffer: &Entity<Buffer>,
26435 position: language::Anchor,
26436 text: &str,
26437 trigger_in_words: bool,
26438 cx: &mut Context<Editor>,
26439 ) -> bool {
26440 let mut chars = text.chars();
26441 let char = if let Some(char) = chars.next() {
26442 char
26443 } else {
26444 return false;
26445 };
26446 if chars.next().is_some() {
26447 return false;
26448 }
26449
26450 let buffer = buffer.read(cx);
26451 let snapshot = buffer.snapshot();
26452 let classifier = snapshot
26453 .char_classifier_at(position)
26454 .scope_context(Some(CharScopeContext::Completion));
26455 if trigger_in_words && classifier.is_word(char) {
26456 return true;
26457 }
26458
26459 buffer.completion_triggers().contains(text)
26460 }
26461
26462 fn show_snippets(&self) -> bool {
26463 true
26464 }
26465}
26466
26467impl SemanticsProvider for Entity<Project> {
26468 fn hover(
26469 &self,
26470 buffer: &Entity<Buffer>,
26471 position: text::Anchor,
26472 cx: &mut App,
26473 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26474 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
26475 }
26476
26477 fn document_highlights(
26478 &self,
26479 buffer: &Entity<Buffer>,
26480 position: text::Anchor,
26481 cx: &mut App,
26482 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
26483 Some(self.update(cx, |project, cx| {
26484 project.document_highlights(buffer, position, cx)
26485 }))
26486 }
26487
26488 fn definitions(
26489 &self,
26490 buffer: &Entity<Buffer>,
26491 position: text::Anchor,
26492 kind: GotoDefinitionKind,
26493 cx: &mut App,
26494 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
26495 Some(self.update(cx, |project, cx| match kind {
26496 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
26497 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
26498 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
26499 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
26500 }))
26501 }
26502
26503 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
26504 self.update(cx, |project, cx| {
26505 if project
26506 .active_debug_session(cx)
26507 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
26508 {
26509 return true;
26510 }
26511
26512 buffer.update(cx, |buffer, cx| {
26513 project.any_language_server_supports_inlay_hints(buffer, cx)
26514 })
26515 })
26516 }
26517
26518 fn inline_values(
26519 &self,
26520 buffer_handle: Entity<Buffer>,
26521 range: Range<text::Anchor>,
26522 cx: &mut App,
26523 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
26524 self.update(cx, |project, cx| {
26525 let (session, active_stack_frame) = project.active_debug_session(cx)?;
26526
26527 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
26528 })
26529 }
26530
26531 fn applicable_inlay_chunks(
26532 &self,
26533 buffer: &Entity<Buffer>,
26534 ranges: &[Range<text::Anchor>],
26535 cx: &mut App,
26536 ) -> Vec<Range<BufferRow>> {
26537 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26538 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
26539 })
26540 }
26541
26542 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
26543 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
26544 lsp_store.invalidate_inlay_hints(for_buffers)
26545 });
26546 }
26547
26548 fn inlay_hints(
26549 &self,
26550 invalidate: InvalidationStrategy,
26551 buffer: Entity<Buffer>,
26552 ranges: Vec<Range<text::Anchor>>,
26553 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26554 cx: &mut App,
26555 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
26556 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26557 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
26558 }))
26559 }
26560
26561 fn range_for_rename(
26562 &self,
26563 buffer: &Entity<Buffer>,
26564 position: text::Anchor,
26565 cx: &mut App,
26566 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
26567 Some(self.update(cx, |project, cx| {
26568 let buffer = buffer.clone();
26569 let task = project.prepare_rename(buffer.clone(), position, cx);
26570 cx.spawn(async move |_, cx| {
26571 Ok(match task.await? {
26572 PrepareRenameResponse::Success(range) => Some(range),
26573 PrepareRenameResponse::InvalidPosition => None,
26574 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
26575 // Fallback on using TreeSitter info to determine identifier range
26576 buffer.read_with(cx, |buffer, _| {
26577 let snapshot = buffer.snapshot();
26578 let (range, kind) = snapshot.surrounding_word(position, None);
26579 if kind != Some(CharKind::Word) {
26580 return None;
26581 }
26582 Some(
26583 snapshot.anchor_before(range.start)
26584 ..snapshot.anchor_after(range.end),
26585 )
26586 })
26587 }
26588 })
26589 })
26590 }))
26591 }
26592
26593 fn perform_rename(
26594 &self,
26595 buffer: &Entity<Buffer>,
26596 position: text::Anchor,
26597 new_name: String,
26598 cx: &mut App,
26599 ) -> Option<Task<Result<ProjectTransaction>>> {
26600 Some(self.update(cx, |project, cx| {
26601 project.perform_rename(buffer.clone(), position, new_name, cx)
26602 }))
26603 }
26604}
26605
26606fn consume_contiguous_rows(
26607 contiguous_row_selections: &mut Vec<Selection<Point>>,
26608 selection: &Selection<Point>,
26609 display_map: &DisplaySnapshot,
26610 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
26611) -> (MultiBufferRow, MultiBufferRow) {
26612 contiguous_row_selections.push(selection.clone());
26613 let start_row = starting_row(selection, display_map);
26614 let mut end_row = ending_row(selection, display_map);
26615
26616 while let Some(next_selection) = selections.peek() {
26617 if next_selection.start.row <= end_row.0 {
26618 end_row = ending_row(next_selection, display_map);
26619 contiguous_row_selections.push(selections.next().unwrap().clone());
26620 } else {
26621 break;
26622 }
26623 }
26624 (start_row, end_row)
26625}
26626
26627fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26628 if selection.start.column > 0 {
26629 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
26630 } else {
26631 MultiBufferRow(selection.start.row)
26632 }
26633}
26634
26635fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26636 if next_selection.end.column > 0 || next_selection.is_empty() {
26637 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
26638 } else {
26639 MultiBufferRow(next_selection.end.row)
26640 }
26641}
26642
26643impl EditorSnapshot {
26644 pub fn remote_selections_in_range<'a>(
26645 &'a self,
26646 range: &'a Range<Anchor>,
26647 collaboration_hub: &dyn CollaborationHub,
26648 cx: &'a App,
26649 ) -> impl 'a + Iterator<Item = RemoteSelection> {
26650 let participant_names = collaboration_hub.user_names(cx);
26651 let participant_indices = collaboration_hub.user_participant_indices(cx);
26652 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
26653 let collaborators_by_replica_id = collaborators_by_peer_id
26654 .values()
26655 .map(|collaborator| (collaborator.replica_id, collaborator))
26656 .collect::<HashMap<_, _>>();
26657 self.buffer_snapshot()
26658 .selections_in_range(range, false)
26659 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
26660 if replica_id == ReplicaId::AGENT {
26661 Some(RemoteSelection {
26662 replica_id,
26663 selection,
26664 cursor_shape,
26665 line_mode,
26666 collaborator_id: CollaboratorId::Agent,
26667 user_name: Some("Agent".into()),
26668 color: cx.theme().players().agent(),
26669 })
26670 } else {
26671 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
26672 let participant_index = participant_indices.get(&collaborator.user_id).copied();
26673 let user_name = participant_names.get(&collaborator.user_id).cloned();
26674 Some(RemoteSelection {
26675 replica_id,
26676 selection,
26677 cursor_shape,
26678 line_mode,
26679 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
26680 user_name,
26681 color: if let Some(index) = participant_index {
26682 cx.theme().players().color_for_participant(index.0)
26683 } else {
26684 cx.theme().players().absent()
26685 },
26686 })
26687 }
26688 })
26689 }
26690
26691 pub fn hunks_for_ranges(
26692 &self,
26693 ranges: impl IntoIterator<Item = Range<Point>>,
26694 ) -> Vec<MultiBufferDiffHunk> {
26695 let mut hunks = Vec::new();
26696 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
26697 HashMap::default();
26698 for query_range in ranges {
26699 let query_rows =
26700 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
26701 for hunk in self.buffer_snapshot().diff_hunks_in_range(
26702 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
26703 ) {
26704 // Include deleted hunks that are adjacent to the query range, because
26705 // otherwise they would be missed.
26706 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
26707 if hunk.status().is_deleted() {
26708 intersects_range |= hunk.row_range.start == query_rows.end;
26709 intersects_range |= hunk.row_range.end == query_rows.start;
26710 }
26711 if intersects_range {
26712 if !processed_buffer_rows
26713 .entry(hunk.buffer_id)
26714 .or_default()
26715 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
26716 {
26717 continue;
26718 }
26719 hunks.push(hunk);
26720 }
26721 }
26722 }
26723
26724 hunks
26725 }
26726
26727 fn display_diff_hunks_for_rows<'a>(
26728 &'a self,
26729 display_rows: Range<DisplayRow>,
26730 folded_buffers: &'a HashSet<BufferId>,
26731 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
26732 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
26733 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
26734
26735 self.buffer_snapshot()
26736 .diff_hunks_in_range(buffer_start..buffer_end)
26737 .filter_map(|hunk| {
26738 if folded_buffers.contains(&hunk.buffer_id) {
26739 return None;
26740 }
26741
26742 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
26743 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
26744
26745 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
26746 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
26747
26748 let display_hunk = if hunk_display_start.column() != 0 {
26749 DisplayDiffHunk::Folded {
26750 display_row: hunk_display_start.row(),
26751 }
26752 } else {
26753 let mut end_row = hunk_display_end.row();
26754 if hunk_display_end.column() > 0 {
26755 end_row.0 += 1;
26756 }
26757 let is_created_file = hunk.is_created_file();
26758
26759 DisplayDiffHunk::Unfolded {
26760 status: hunk.status(),
26761 diff_base_byte_range: hunk.diff_base_byte_range.start.0
26762 ..hunk.diff_base_byte_range.end.0,
26763 word_diffs: hunk.word_diffs,
26764 display_row_range: hunk_display_start.row()..end_row,
26765 multi_buffer_range: Anchor::range_in_buffer(
26766 hunk.excerpt_id,
26767 hunk.buffer_range,
26768 ),
26769 is_created_file,
26770 }
26771 };
26772
26773 Some(display_hunk)
26774 })
26775 }
26776
26777 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
26778 self.display_snapshot
26779 .buffer_snapshot()
26780 .language_at(position)
26781 }
26782
26783 pub fn is_focused(&self) -> bool {
26784 self.is_focused
26785 }
26786
26787 pub fn placeholder_text(&self) -> Option<String> {
26788 self.placeholder_display_snapshot
26789 .as_ref()
26790 .map(|display_map| display_map.text())
26791 }
26792
26793 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
26794 self.scroll_anchor.scroll_position(&self.display_snapshot)
26795 }
26796
26797 pub fn gutter_dimensions(
26798 &self,
26799 font_id: FontId,
26800 font_size: Pixels,
26801 style: &EditorStyle,
26802 window: &mut Window,
26803 cx: &App,
26804 ) -> GutterDimensions {
26805 if self.show_gutter
26806 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
26807 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
26808 {
26809 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
26810 matches!(
26811 ProjectSettings::get_global(cx).git.git_gutter,
26812 GitGutterSetting::TrackedFiles
26813 )
26814 });
26815 let gutter_settings = EditorSettings::get_global(cx).gutter;
26816 let show_line_numbers = self
26817 .show_line_numbers
26818 .unwrap_or(gutter_settings.line_numbers);
26819 let line_gutter_width = if show_line_numbers {
26820 // Avoid flicker-like gutter resizes when the line number gains another digit by
26821 // only resizing the gutter on files with > 10**min_line_number_digits lines.
26822 let min_width_for_number_on_gutter =
26823 ch_advance * gutter_settings.min_line_number_digits as f32;
26824 self.max_line_number_width(style, window)
26825 .max(min_width_for_number_on_gutter)
26826 } else {
26827 0.0.into()
26828 };
26829
26830 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
26831 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
26832
26833 let git_blame_entries_width =
26834 self.git_blame_gutter_max_author_length
26835 .map(|max_author_length| {
26836 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
26837 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
26838
26839 /// The number of characters to dedicate to gaps and margins.
26840 const SPACING_WIDTH: usize = 4;
26841
26842 let max_char_count = max_author_length.min(renderer.max_author_length())
26843 + ::git::SHORT_SHA_LENGTH
26844 + MAX_RELATIVE_TIMESTAMP.len()
26845 + SPACING_WIDTH;
26846
26847 ch_advance * max_char_count
26848 });
26849
26850 let is_singleton = self.buffer_snapshot().is_singleton();
26851
26852 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
26853 left_padding += if !is_singleton {
26854 ch_width * 4.0
26855 } else if show_runnables || show_breakpoints {
26856 ch_width * 3.0
26857 } else if show_git_gutter && show_line_numbers {
26858 ch_width * 2.0
26859 } else if show_git_gutter || show_line_numbers {
26860 ch_width
26861 } else {
26862 px(0.)
26863 };
26864
26865 let shows_folds = is_singleton && gutter_settings.folds;
26866
26867 let right_padding = if shows_folds && show_line_numbers {
26868 ch_width * 4.0
26869 } else if shows_folds || (!is_singleton && show_line_numbers) {
26870 ch_width * 3.0
26871 } else if show_line_numbers {
26872 ch_width
26873 } else {
26874 px(0.)
26875 };
26876
26877 GutterDimensions {
26878 left_padding,
26879 right_padding,
26880 width: line_gutter_width + left_padding + right_padding,
26881 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
26882 git_blame_entries_width,
26883 }
26884 } else if self.offset_content {
26885 GutterDimensions::default_with_margin(font_id, font_size, cx)
26886 } else {
26887 GutterDimensions::default()
26888 }
26889 }
26890
26891 pub fn render_crease_toggle(
26892 &self,
26893 buffer_row: MultiBufferRow,
26894 row_contains_cursor: bool,
26895 editor: Entity<Editor>,
26896 window: &mut Window,
26897 cx: &mut App,
26898 ) -> Option<AnyElement> {
26899 let folded = self.is_line_folded(buffer_row);
26900 let mut is_foldable = false;
26901
26902 if let Some(crease) = self
26903 .crease_snapshot
26904 .query_row(buffer_row, self.buffer_snapshot())
26905 {
26906 is_foldable = true;
26907 match crease {
26908 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
26909 if let Some(render_toggle) = render_toggle {
26910 let toggle_callback =
26911 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
26912 if folded {
26913 editor.update(cx, |editor, cx| {
26914 editor.fold_at(buffer_row, window, cx)
26915 });
26916 } else {
26917 editor.update(cx, |editor, cx| {
26918 editor.unfold_at(buffer_row, window, cx)
26919 });
26920 }
26921 });
26922 return Some((render_toggle)(
26923 buffer_row,
26924 folded,
26925 toggle_callback,
26926 window,
26927 cx,
26928 ));
26929 }
26930 }
26931 }
26932 }
26933
26934 is_foldable |= self.starts_indent(buffer_row);
26935
26936 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
26937 Some(
26938 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
26939 .toggle_state(folded)
26940 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
26941 if folded {
26942 this.unfold_at(buffer_row, window, cx);
26943 } else {
26944 this.fold_at(buffer_row, window, cx);
26945 }
26946 }))
26947 .into_any_element(),
26948 )
26949 } else {
26950 None
26951 }
26952 }
26953
26954 pub fn render_crease_trailer(
26955 &self,
26956 buffer_row: MultiBufferRow,
26957 window: &mut Window,
26958 cx: &mut App,
26959 ) -> Option<AnyElement> {
26960 let folded = self.is_line_folded(buffer_row);
26961 if let Crease::Inline { render_trailer, .. } = self
26962 .crease_snapshot
26963 .query_row(buffer_row, self.buffer_snapshot())?
26964 {
26965 let render_trailer = render_trailer.as_ref()?;
26966 Some(render_trailer(buffer_row, folded, window, cx))
26967 } else {
26968 None
26969 }
26970 }
26971
26972 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
26973 let digit_count = self.widest_line_number().ilog10() + 1;
26974 column_pixels(style, digit_count as usize, window)
26975 }
26976
26977 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
26978 ///
26979 /// This is positive if `base` is before `line`.
26980 fn relative_line_delta(
26981 &self,
26982 current_selection_head: DisplayRow,
26983 first_visible_row: DisplayRow,
26984 consider_wrapped_lines: bool,
26985 ) -> i64 {
26986 let current_selection_head = current_selection_head.as_display_point().to_point(self);
26987 let first_visible_row = first_visible_row.as_display_point().to_point(self);
26988
26989 if consider_wrapped_lines {
26990 let wrap_snapshot = self.wrap_snapshot();
26991 let base_wrap_row = wrap_snapshot
26992 .make_wrap_point(current_selection_head, Bias::Left)
26993 .row();
26994 let wrap_row = wrap_snapshot
26995 .make_wrap_point(first_visible_row, Bias::Left)
26996 .row();
26997
26998 wrap_row.0 as i64 - base_wrap_row.0 as i64
26999 } else {
27000 let fold_snapshot = self.fold_snapshot();
27001 let base_fold_row = fold_snapshot
27002 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27003 .row();
27004 let fold_row = fold_snapshot
27005 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27006 .row();
27007
27008 fold_row as i64 - base_fold_row as i64
27009 }
27010 }
27011
27012 /// Returns the unsigned relative line number to display for each row in `rows`.
27013 ///
27014 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27015 pub fn calculate_relative_line_numbers(
27016 &self,
27017 rows: &Range<DisplayRow>,
27018 current_selection_head: DisplayRow,
27019 count_wrapped_lines: bool,
27020 ) -> HashMap<DisplayRow, u32> {
27021 let initial_offset =
27022 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27023 let current_selection_point = current_selection_head.as_display_point().to_point(self);
27024
27025 self.row_infos(rows.start)
27026 .take(rows.len())
27027 .enumerate()
27028 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27029 .filter(|(_row, row_info)| {
27030 row_info.buffer_row.is_some()
27031 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27032 })
27033 .enumerate()
27034 .filter(|(_, (row, row_info))| {
27035 // We want to check here that
27036 // - the row is not the current selection head to ensure the current
27037 // line has absolute numbering
27038 // - similarly, should the selection head live in a soft-wrapped line
27039 // and we are not counting those, that the parent line keeps its
27040 // absolute number
27041 // - lastly, if we are in a deleted line, it is fine to number this
27042 // relative with 0, as otherwise it would have no line number at all
27043 (*row != current_selection_head
27044 && (count_wrapped_lines
27045 || row_info.buffer_row != Some(current_selection_point.row)))
27046 || row_info
27047 .diff_status
27048 .is_some_and(|status| status.is_deleted())
27049 })
27050 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
27051 .collect()
27052 }
27053}
27054
27055pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27056 let font_size = style.text.font_size.to_pixels(window.rem_size());
27057 let layout = window.text_system().shape_line(
27058 SharedString::from(" ".repeat(column)),
27059 font_size,
27060 &[TextRun {
27061 len: column,
27062 font: style.text.font(),
27063 color: Hsla::default(),
27064 ..Default::default()
27065 }],
27066 None,
27067 );
27068
27069 layout.width
27070}
27071
27072impl Deref for EditorSnapshot {
27073 type Target = DisplaySnapshot;
27074
27075 fn deref(&self) -> &Self::Target {
27076 &self.display_snapshot
27077 }
27078}
27079
27080#[derive(Clone, Debug, PartialEq, Eq)]
27081pub enum EditorEvent {
27082 /// Emitted when the stored review comments change (added, removed, or updated).
27083 ReviewCommentsChanged {
27084 /// The new total count of review comments.
27085 total_count: usize,
27086 },
27087 InputIgnored {
27088 text: Arc<str>,
27089 },
27090 InputHandled {
27091 utf16_range_to_replace: Option<Range<isize>>,
27092 text: Arc<str>,
27093 },
27094 ExcerptsAdded {
27095 buffer: Entity<Buffer>,
27096 predecessor: ExcerptId,
27097 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27098 },
27099 ExcerptsRemoved {
27100 ids: Vec<ExcerptId>,
27101 removed_buffer_ids: Vec<BufferId>,
27102 },
27103 BufferFoldToggled {
27104 ids: Vec<ExcerptId>,
27105 folded: bool,
27106 },
27107 ExcerptsEdited {
27108 ids: Vec<ExcerptId>,
27109 },
27110 ExcerptsExpanded {
27111 ids: Vec<ExcerptId>,
27112 },
27113 ExpandExcerptsRequested {
27114 excerpt_ids: Vec<ExcerptId>,
27115 lines: u32,
27116 direction: ExpandExcerptDirection,
27117 },
27118 BufferEdited,
27119 Edited {
27120 transaction_id: clock::Lamport,
27121 },
27122 Reparsed(BufferId),
27123 Focused,
27124 FocusedIn,
27125 Blurred,
27126 DirtyChanged,
27127 Saved,
27128 TitleChanged,
27129 SelectionsChanged {
27130 local: bool,
27131 },
27132 ScrollPositionChanged {
27133 local: bool,
27134 autoscroll: bool,
27135 },
27136 TransactionUndone {
27137 transaction_id: clock::Lamport,
27138 },
27139 TransactionBegun {
27140 transaction_id: clock::Lamport,
27141 },
27142 CursorShapeChanged,
27143 BreadcrumbsChanged,
27144 PushedToNavHistory {
27145 anchor: Anchor,
27146 is_deactivate: bool,
27147 },
27148}
27149
27150impl EventEmitter<EditorEvent> for Editor {}
27151
27152impl Focusable for Editor {
27153 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27154 self.focus_handle.clone()
27155 }
27156}
27157
27158impl Render for Editor {
27159 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27160 EditorElement::new(&cx.entity(), self.create_style(cx))
27161 }
27162}
27163
27164impl EntityInputHandler for Editor {
27165 fn text_for_range(
27166 &mut self,
27167 range_utf16: Range<usize>,
27168 adjusted_range: &mut Option<Range<usize>>,
27169 _: &mut Window,
27170 cx: &mut Context<Self>,
27171 ) -> Option<String> {
27172 let snapshot = self.buffer.read(cx).read(cx);
27173 let start = snapshot.clip_offset_utf16(
27174 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27175 Bias::Left,
27176 );
27177 let end = snapshot.clip_offset_utf16(
27178 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27179 Bias::Right,
27180 );
27181 if (start.0.0..end.0.0) != range_utf16 {
27182 adjusted_range.replace(start.0.0..end.0.0);
27183 }
27184 Some(snapshot.text_for_range(start..end).collect())
27185 }
27186
27187 fn selected_text_range(
27188 &mut self,
27189 ignore_disabled_input: bool,
27190 _: &mut Window,
27191 cx: &mut Context<Self>,
27192 ) -> Option<UTF16Selection> {
27193 // Prevent the IME menu from appearing when holding down an alphabetic key
27194 // while input is disabled.
27195 if !ignore_disabled_input && !self.input_enabled {
27196 return None;
27197 }
27198
27199 let selection = self
27200 .selections
27201 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27202 let range = selection.range();
27203
27204 Some(UTF16Selection {
27205 range: range.start.0.0..range.end.0.0,
27206 reversed: selection.reversed,
27207 })
27208 }
27209
27210 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27211 let snapshot = self.buffer.read(cx).read(cx);
27212 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
27213 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27214 }
27215
27216 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27217 self.clear_highlights::<InputComposition>(cx);
27218 self.ime_transaction.take();
27219 }
27220
27221 fn replace_text_in_range(
27222 &mut self,
27223 range_utf16: Option<Range<usize>>,
27224 text: &str,
27225 window: &mut Window,
27226 cx: &mut Context<Self>,
27227 ) {
27228 if !self.input_enabled {
27229 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27230 return;
27231 }
27232
27233 self.transact(window, cx, |this, window, cx| {
27234 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27235 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27236 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27237 Some(this.selection_replacement_ranges(range_utf16, cx))
27238 } else {
27239 this.marked_text_ranges(cx)
27240 };
27241
27242 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27243 let newest_selection_id = this.selections.newest_anchor().id;
27244 this.selections
27245 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27246 .iter()
27247 .zip(ranges_to_replace.iter())
27248 .find_map(|(selection, range)| {
27249 if selection.id == newest_selection_id {
27250 Some(
27251 (range.start.0.0 as isize - selection.head().0.0 as isize)
27252 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27253 )
27254 } else {
27255 None
27256 }
27257 })
27258 });
27259
27260 cx.emit(EditorEvent::InputHandled {
27261 utf16_range_to_replace: range_to_replace,
27262 text: text.into(),
27263 });
27264
27265 if let Some(new_selected_ranges) = new_selected_ranges {
27266 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27267 selections.select_ranges(new_selected_ranges)
27268 });
27269 this.backspace(&Default::default(), window, cx);
27270 }
27271
27272 this.handle_input(text, window, cx);
27273 });
27274
27275 if let Some(transaction) = self.ime_transaction {
27276 self.buffer.update(cx, |buffer, cx| {
27277 buffer.group_until_transaction(transaction, cx);
27278 });
27279 }
27280
27281 self.unmark_text(window, cx);
27282 }
27283
27284 fn replace_and_mark_text_in_range(
27285 &mut self,
27286 range_utf16: Option<Range<usize>>,
27287 text: &str,
27288 new_selected_range_utf16: Option<Range<usize>>,
27289 window: &mut Window,
27290 cx: &mut Context<Self>,
27291 ) {
27292 if !self.input_enabled {
27293 return;
27294 }
27295
27296 let transaction = self.transact(window, cx, |this, window, cx| {
27297 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27298 let snapshot = this.buffer.read(cx).read(cx);
27299 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27300 for marked_range in &mut marked_ranges {
27301 marked_range.end = marked_range.start + relative_range_utf16.end;
27302 marked_range.start += relative_range_utf16.start;
27303 marked_range.start =
27304 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27305 marked_range.end =
27306 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27307 }
27308 }
27309 Some(marked_ranges)
27310 } else if let Some(range_utf16) = range_utf16 {
27311 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27312 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27313 Some(this.selection_replacement_ranges(range_utf16, cx))
27314 } else {
27315 None
27316 };
27317
27318 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27319 let newest_selection_id = this.selections.newest_anchor().id;
27320 this.selections
27321 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27322 .iter()
27323 .zip(ranges_to_replace.iter())
27324 .find_map(|(selection, range)| {
27325 if selection.id == newest_selection_id {
27326 Some(
27327 (range.start.0.0 as isize - selection.head().0.0 as isize)
27328 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27329 )
27330 } else {
27331 None
27332 }
27333 })
27334 });
27335
27336 cx.emit(EditorEvent::InputHandled {
27337 utf16_range_to_replace: range_to_replace,
27338 text: text.into(),
27339 });
27340
27341 if let Some(ranges) = ranges_to_replace {
27342 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27343 s.select_ranges(ranges)
27344 });
27345 }
27346
27347 let marked_ranges = {
27348 let snapshot = this.buffer.read(cx).read(cx);
27349 this.selections
27350 .disjoint_anchors_arc()
27351 .iter()
27352 .map(|selection| {
27353 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27354 })
27355 .collect::<Vec<_>>()
27356 };
27357
27358 if text.is_empty() {
27359 this.unmark_text(window, cx);
27360 } else {
27361 this.highlight_text::<InputComposition>(
27362 marked_ranges.clone(),
27363 HighlightStyle {
27364 underline: Some(UnderlineStyle {
27365 thickness: px(1.),
27366 color: None,
27367 wavy: false,
27368 }),
27369 ..Default::default()
27370 },
27371 cx,
27372 );
27373 }
27374
27375 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27376 let use_autoclose = this.use_autoclose;
27377 let use_auto_surround = this.use_auto_surround;
27378 this.set_use_autoclose(false);
27379 this.set_use_auto_surround(false);
27380 this.handle_input(text, window, cx);
27381 this.set_use_autoclose(use_autoclose);
27382 this.set_use_auto_surround(use_auto_surround);
27383
27384 if let Some(new_selected_range) = new_selected_range_utf16 {
27385 let snapshot = this.buffer.read(cx).read(cx);
27386 let new_selected_ranges = marked_ranges
27387 .into_iter()
27388 .map(|marked_range| {
27389 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27390 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27391 insertion_start.0 + new_selected_range.start,
27392 ));
27393 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27394 insertion_start.0 + new_selected_range.end,
27395 ));
27396 snapshot.clip_offset_utf16(new_start, Bias::Left)
27397 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27398 })
27399 .collect::<Vec<_>>();
27400
27401 drop(snapshot);
27402 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27403 selections.select_ranges(new_selected_ranges)
27404 });
27405 }
27406 });
27407
27408 self.ime_transaction = self.ime_transaction.or(transaction);
27409 if let Some(transaction) = self.ime_transaction {
27410 self.buffer.update(cx, |buffer, cx| {
27411 buffer.group_until_transaction(transaction, cx);
27412 });
27413 }
27414
27415 if self.text_highlights::<InputComposition>(cx).is_none() {
27416 self.ime_transaction.take();
27417 }
27418 }
27419
27420 fn bounds_for_range(
27421 &mut self,
27422 range_utf16: Range<usize>,
27423 element_bounds: gpui::Bounds<Pixels>,
27424 window: &mut Window,
27425 cx: &mut Context<Self>,
27426 ) -> Option<gpui::Bounds<Pixels>> {
27427 let text_layout_details = self.text_layout_details(window);
27428 let CharacterDimensions {
27429 em_width,
27430 em_advance,
27431 line_height,
27432 } = self.character_dimensions(window);
27433
27434 let snapshot = self.snapshot(window, cx);
27435 let scroll_position = snapshot.scroll_position();
27436 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
27437
27438 let start =
27439 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
27440 let x = Pixels::from(
27441 ScrollOffset::from(
27442 snapshot.x_for_display_point(start, &text_layout_details)
27443 + self.gutter_dimensions.full_width(),
27444 ) - scroll_left,
27445 );
27446 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
27447
27448 Some(Bounds {
27449 origin: element_bounds.origin + point(x, y),
27450 size: size(em_width, line_height),
27451 })
27452 }
27453
27454 fn character_index_for_point(
27455 &mut self,
27456 point: gpui::Point<Pixels>,
27457 _window: &mut Window,
27458 _cx: &mut Context<Self>,
27459 ) -> Option<usize> {
27460 let position_map = self.last_position_map.as_ref()?;
27461 if !position_map.text_hitbox.contains(&point) {
27462 return None;
27463 }
27464 let display_point = position_map.point_for_position(point).previous_valid;
27465 let anchor = position_map
27466 .snapshot
27467 .display_point_to_anchor(display_point, Bias::Left);
27468 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
27469 Some(utf16_offset.0.0)
27470 }
27471
27472 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
27473 self.input_enabled
27474 }
27475}
27476
27477trait SelectionExt {
27478 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
27479 fn spanned_rows(
27480 &self,
27481 include_end_if_at_line_start: bool,
27482 map: &DisplaySnapshot,
27483 ) -> Range<MultiBufferRow>;
27484}
27485
27486impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
27487 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
27488 let start = self
27489 .start
27490 .to_point(map.buffer_snapshot())
27491 .to_display_point(map);
27492 let end = self
27493 .end
27494 .to_point(map.buffer_snapshot())
27495 .to_display_point(map);
27496 if self.reversed {
27497 end..start
27498 } else {
27499 start..end
27500 }
27501 }
27502
27503 fn spanned_rows(
27504 &self,
27505 include_end_if_at_line_start: bool,
27506 map: &DisplaySnapshot,
27507 ) -> Range<MultiBufferRow> {
27508 let start = self.start.to_point(map.buffer_snapshot());
27509 let mut end = self.end.to_point(map.buffer_snapshot());
27510 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
27511 end.row -= 1;
27512 }
27513
27514 let buffer_start = map.prev_line_boundary(start).0;
27515 let buffer_end = map.next_line_boundary(end).0;
27516 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
27517 }
27518}
27519
27520impl<T: InvalidationRegion> InvalidationStack<T> {
27521 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
27522 where
27523 S: Clone + ToOffset,
27524 {
27525 while let Some(region) = self.last() {
27526 let all_selections_inside_invalidation_ranges =
27527 if selections.len() == region.ranges().len() {
27528 selections
27529 .iter()
27530 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
27531 .all(|(selection, invalidation_range)| {
27532 let head = selection.head().to_offset(buffer);
27533 invalidation_range.start <= head && invalidation_range.end >= head
27534 })
27535 } else {
27536 false
27537 };
27538
27539 if all_selections_inside_invalidation_ranges {
27540 break;
27541 } else {
27542 self.pop();
27543 }
27544 }
27545 }
27546}
27547
27548impl<T> Default for InvalidationStack<T> {
27549 fn default() -> Self {
27550 Self(Default::default())
27551 }
27552}
27553
27554impl<T> Deref for InvalidationStack<T> {
27555 type Target = Vec<T>;
27556
27557 fn deref(&self) -> &Self::Target {
27558 &self.0
27559 }
27560}
27561
27562impl<T> DerefMut for InvalidationStack<T> {
27563 fn deref_mut(&mut self) -> &mut Self::Target {
27564 &mut self.0
27565 }
27566}
27567
27568impl InvalidationRegion for SnippetState {
27569 fn ranges(&self) -> &[Range<Anchor>] {
27570 &self.ranges[self.active_index]
27571 }
27572}
27573
27574fn edit_prediction_edit_text(
27575 current_snapshot: &BufferSnapshot,
27576 edits: &[(Range<Anchor>, impl AsRef<str>)],
27577 edit_preview: &EditPreview,
27578 include_deletions: bool,
27579 cx: &App,
27580) -> HighlightedText {
27581 let edits = edits
27582 .iter()
27583 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
27584 .collect::<Vec<_>>();
27585
27586 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
27587}
27588
27589fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
27590 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
27591 // Just show the raw edit text with basic styling
27592 let mut text = String::new();
27593 let mut highlights = Vec::new();
27594
27595 let insertion_highlight_style = HighlightStyle {
27596 color: Some(cx.theme().colors().text),
27597 ..Default::default()
27598 };
27599
27600 for (_, edit_text) in edits {
27601 let start_offset = text.len();
27602 text.push_str(edit_text);
27603 let end_offset = text.len();
27604
27605 if start_offset < end_offset {
27606 highlights.push((start_offset..end_offset, insertion_highlight_style));
27607 }
27608 }
27609
27610 HighlightedText {
27611 text: text.into(),
27612 highlights,
27613 }
27614}
27615
27616pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
27617 match severity {
27618 lsp::DiagnosticSeverity::ERROR => colors.error,
27619 lsp::DiagnosticSeverity::WARNING => colors.warning,
27620 lsp::DiagnosticSeverity::INFORMATION => colors.info,
27621 lsp::DiagnosticSeverity::HINT => colors.info,
27622 _ => colors.ignored,
27623 }
27624}
27625
27626pub fn styled_runs_for_code_label<'a>(
27627 label: &'a CodeLabel,
27628 syntax_theme: &'a theme::SyntaxTheme,
27629 local_player: &'a theme::PlayerColor,
27630) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
27631 let fade_out = HighlightStyle {
27632 fade_out: Some(0.35),
27633 ..Default::default()
27634 };
27635
27636 let mut prev_end = label.filter_range.end;
27637 label
27638 .runs
27639 .iter()
27640 .enumerate()
27641 .flat_map(move |(ix, (range, highlight_id))| {
27642 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
27643 HighlightStyle {
27644 color: Some(local_player.cursor),
27645 ..Default::default()
27646 }
27647 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
27648 HighlightStyle {
27649 background_color: Some(local_player.selection),
27650 ..Default::default()
27651 }
27652 } else if let Some(style) = highlight_id.style(syntax_theme) {
27653 style
27654 } else {
27655 return Default::default();
27656 };
27657 let muted_style = style.highlight(fade_out);
27658
27659 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
27660 if range.start >= label.filter_range.end {
27661 if range.start > prev_end {
27662 runs.push((prev_end..range.start, fade_out));
27663 }
27664 runs.push((range.clone(), muted_style));
27665 } else if range.end <= label.filter_range.end {
27666 runs.push((range.clone(), style));
27667 } else {
27668 runs.push((range.start..label.filter_range.end, style));
27669 runs.push((label.filter_range.end..range.end, muted_style));
27670 }
27671 prev_end = cmp::max(prev_end, range.end);
27672
27673 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
27674 runs.push((prev_end..label.text.len(), fade_out));
27675 }
27676
27677 runs
27678 })
27679}
27680
27681pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
27682 let mut prev_index = 0;
27683 let mut prev_codepoint: Option<char> = None;
27684 text.char_indices()
27685 .chain([(text.len(), '\0')])
27686 .filter_map(move |(index, codepoint)| {
27687 let prev_codepoint = prev_codepoint.replace(codepoint)?;
27688 let is_boundary = index == text.len()
27689 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
27690 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
27691 if is_boundary {
27692 let chunk = &text[prev_index..index];
27693 prev_index = index;
27694 Some(chunk)
27695 } else {
27696 None
27697 }
27698 })
27699}
27700
27701/// Given a string of text immediately before the cursor, iterates over possible
27702/// strings a snippet could match to. More precisely: returns an iterator over
27703/// suffixes of `text` created by splitting at word boundaries (before & after
27704/// every non-word character).
27705///
27706/// Shorter suffixes are returned first.
27707pub(crate) fn snippet_candidate_suffixes(
27708 text: &str,
27709 is_word_char: impl Fn(char) -> bool,
27710) -> impl std::iter::Iterator<Item = &str> {
27711 let mut prev_index = text.len();
27712 let mut prev_codepoint = None;
27713 text.char_indices()
27714 .rev()
27715 .chain([(0, '\0')])
27716 .filter_map(move |(index, codepoint)| {
27717 let prev_index = std::mem::replace(&mut prev_index, index);
27718 let prev_codepoint = prev_codepoint.replace(codepoint)?;
27719 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
27720 None
27721 } else {
27722 let chunk = &text[prev_index..]; // go to end of string
27723 Some(chunk)
27724 }
27725 })
27726}
27727
27728pub trait RangeToAnchorExt: Sized {
27729 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
27730
27731 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
27732 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
27733 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
27734 }
27735}
27736
27737impl<T: ToOffset> RangeToAnchorExt for Range<T> {
27738 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
27739 let start_offset = self.start.to_offset(snapshot);
27740 let end_offset = self.end.to_offset(snapshot);
27741 if start_offset == end_offset {
27742 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
27743 } else {
27744 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
27745 }
27746 }
27747}
27748
27749pub trait RowExt {
27750 fn as_f64(&self) -> f64;
27751
27752 fn next_row(&self) -> Self;
27753
27754 fn previous_row(&self) -> Self;
27755
27756 fn minus(&self, other: Self) -> u32;
27757}
27758
27759impl RowExt for DisplayRow {
27760 fn as_f64(&self) -> f64 {
27761 self.0 as _
27762 }
27763
27764 fn next_row(&self) -> Self {
27765 Self(self.0 + 1)
27766 }
27767
27768 fn previous_row(&self) -> Self {
27769 Self(self.0.saturating_sub(1))
27770 }
27771
27772 fn minus(&self, other: Self) -> u32 {
27773 self.0 - other.0
27774 }
27775}
27776
27777impl RowExt for MultiBufferRow {
27778 fn as_f64(&self) -> f64 {
27779 self.0 as _
27780 }
27781
27782 fn next_row(&self) -> Self {
27783 Self(self.0 + 1)
27784 }
27785
27786 fn previous_row(&self) -> Self {
27787 Self(self.0.saturating_sub(1))
27788 }
27789
27790 fn minus(&self, other: Self) -> u32 {
27791 self.0 - other.0
27792 }
27793}
27794
27795trait RowRangeExt {
27796 type Row;
27797
27798 fn len(&self) -> usize;
27799
27800 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
27801}
27802
27803impl RowRangeExt for Range<MultiBufferRow> {
27804 type Row = MultiBufferRow;
27805
27806 fn len(&self) -> usize {
27807 (self.end.0 - self.start.0) as usize
27808 }
27809
27810 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
27811 (self.start.0..self.end.0).map(MultiBufferRow)
27812 }
27813}
27814
27815impl RowRangeExt for Range<DisplayRow> {
27816 type Row = DisplayRow;
27817
27818 fn len(&self) -> usize {
27819 (self.end.0 - self.start.0) as usize
27820 }
27821
27822 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
27823 (self.start.0..self.end.0).map(DisplayRow)
27824 }
27825}
27826
27827/// If select range has more than one line, we
27828/// just point the cursor to range.start.
27829fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
27830 if range.start.row == range.end.row {
27831 range
27832 } else {
27833 range.start..range.start
27834 }
27835}
27836pub struct KillRing(ClipboardItem);
27837impl Global for KillRing {}
27838
27839const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
27840
27841enum BreakpointPromptEditAction {
27842 Log,
27843 Condition,
27844 HitCondition,
27845}
27846
27847struct BreakpointPromptEditor {
27848 pub(crate) prompt: Entity<Editor>,
27849 editor: WeakEntity<Editor>,
27850 breakpoint_anchor: Anchor,
27851 breakpoint: Breakpoint,
27852 edit_action: BreakpointPromptEditAction,
27853 block_ids: HashSet<CustomBlockId>,
27854 editor_margins: Arc<Mutex<EditorMargins>>,
27855 _subscriptions: Vec<Subscription>,
27856}
27857
27858impl BreakpointPromptEditor {
27859 const MAX_LINES: u8 = 4;
27860
27861 fn new(
27862 editor: WeakEntity<Editor>,
27863 breakpoint_anchor: Anchor,
27864 breakpoint: Breakpoint,
27865 edit_action: BreakpointPromptEditAction,
27866 window: &mut Window,
27867 cx: &mut Context<Self>,
27868 ) -> Self {
27869 let base_text = match edit_action {
27870 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
27871 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
27872 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
27873 }
27874 .map(|msg| msg.to_string())
27875 .unwrap_or_default();
27876
27877 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
27878 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27879
27880 let prompt = cx.new(|cx| {
27881 let mut prompt = Editor::new(
27882 EditorMode::AutoHeight {
27883 min_lines: 1,
27884 max_lines: Some(Self::MAX_LINES as usize),
27885 },
27886 buffer,
27887 None,
27888 window,
27889 cx,
27890 );
27891 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
27892 prompt.set_show_cursor_when_unfocused(false, cx);
27893 prompt.set_placeholder_text(
27894 match edit_action {
27895 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
27896 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
27897 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
27898 },
27899 window,
27900 cx,
27901 );
27902
27903 prompt
27904 });
27905
27906 Self {
27907 prompt,
27908 editor,
27909 breakpoint_anchor,
27910 breakpoint,
27911 edit_action,
27912 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
27913 block_ids: Default::default(),
27914 _subscriptions: vec![],
27915 }
27916 }
27917
27918 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
27919 self.block_ids.extend(block_ids)
27920 }
27921
27922 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
27923 if let Some(editor) = self.editor.upgrade() {
27924 let message = self
27925 .prompt
27926 .read(cx)
27927 .buffer
27928 .read(cx)
27929 .as_singleton()
27930 .expect("A multi buffer in breakpoint prompt isn't possible")
27931 .read(cx)
27932 .as_rope()
27933 .to_string();
27934
27935 editor.update(cx, |editor, cx| {
27936 editor.edit_breakpoint_at_anchor(
27937 self.breakpoint_anchor,
27938 self.breakpoint.clone(),
27939 match self.edit_action {
27940 BreakpointPromptEditAction::Log => {
27941 BreakpointEditAction::EditLogMessage(message.into())
27942 }
27943 BreakpointPromptEditAction::Condition => {
27944 BreakpointEditAction::EditCondition(message.into())
27945 }
27946 BreakpointPromptEditAction::HitCondition => {
27947 BreakpointEditAction::EditHitCondition(message.into())
27948 }
27949 },
27950 cx,
27951 );
27952
27953 editor.remove_blocks(self.block_ids.clone(), None, cx);
27954 cx.focus_self(window);
27955 });
27956 }
27957 }
27958
27959 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
27960 self.editor
27961 .update(cx, |editor, cx| {
27962 editor.remove_blocks(self.block_ids.clone(), None, cx);
27963 window.focus(&editor.focus_handle, cx);
27964 })
27965 .log_err();
27966 }
27967
27968 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
27969 let settings = ThemeSettings::get_global(cx);
27970 let text_style = TextStyle {
27971 color: if self.prompt.read(cx).read_only(cx) {
27972 cx.theme().colors().text_disabled
27973 } else {
27974 cx.theme().colors().text
27975 },
27976 font_family: settings.buffer_font.family.clone(),
27977 font_fallbacks: settings.buffer_font.fallbacks.clone(),
27978 font_size: settings.buffer_font_size(cx).into(),
27979 font_weight: settings.buffer_font.weight,
27980 line_height: relative(settings.buffer_line_height.value()),
27981 ..Default::default()
27982 };
27983 EditorElement::new(
27984 &self.prompt,
27985 EditorStyle {
27986 background: cx.theme().colors().editor_background,
27987 local_player: cx.theme().players().local(),
27988 text: text_style,
27989 ..Default::default()
27990 },
27991 )
27992 }
27993}
27994
27995impl Render for BreakpointPromptEditor {
27996 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27997 let editor_margins = *self.editor_margins.lock();
27998 let gutter_dimensions = editor_margins.gutter;
27999 h_flex()
28000 .key_context("Editor")
28001 .bg(cx.theme().colors().editor_background)
28002 .border_y_1()
28003 .border_color(cx.theme().status().info_border)
28004 .size_full()
28005 .py(window.line_height() / 2.5)
28006 .on_action(cx.listener(Self::confirm))
28007 .on_action(cx.listener(Self::cancel))
28008 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28009 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28010 }
28011}
28012
28013impl Focusable for BreakpointPromptEditor {
28014 fn focus_handle(&self, cx: &App) -> FocusHandle {
28015 self.prompt.focus_handle(cx)
28016 }
28017}
28018
28019fn all_edits_insertions_or_deletions(
28020 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28021 snapshot: &MultiBufferSnapshot,
28022) -> bool {
28023 let mut all_insertions = true;
28024 let mut all_deletions = true;
28025
28026 for (range, new_text) in edits.iter() {
28027 let range_is_empty = range.to_offset(snapshot).is_empty();
28028 let text_is_empty = new_text.is_empty();
28029
28030 if range_is_empty != text_is_empty {
28031 if range_is_empty {
28032 all_deletions = false;
28033 } else {
28034 all_insertions = false;
28035 }
28036 } else {
28037 return false;
28038 }
28039
28040 if !all_insertions && !all_deletions {
28041 return false;
28042 }
28043 }
28044 all_insertions || all_deletions
28045}
28046
28047struct MissingEditPredictionKeybindingTooltip;
28048
28049impl Render for MissingEditPredictionKeybindingTooltip {
28050 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28051 ui::tooltip_container(cx, |container, cx| {
28052 container
28053 .flex_shrink_0()
28054 .max_w_80()
28055 .min_h(rems_from_px(124.))
28056 .justify_between()
28057 .child(
28058 v_flex()
28059 .flex_1()
28060 .text_ui_sm(cx)
28061 .child(Label::new("Conflict with Accept Keybinding"))
28062 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28063 )
28064 .child(
28065 h_flex()
28066 .pb_1()
28067 .gap_1()
28068 .items_end()
28069 .w_full()
28070 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28071 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28072 }))
28073 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28074 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28075 })),
28076 )
28077 })
28078 }
28079}
28080
28081#[derive(Debug, Clone, Copy, PartialEq)]
28082pub struct LineHighlight {
28083 pub background: Background,
28084 pub border: Option<gpui::Hsla>,
28085 pub include_gutter: bool,
28086 pub type_id: Option<TypeId>,
28087}
28088
28089struct LineManipulationResult {
28090 pub new_text: String,
28091 pub line_count_before: usize,
28092 pub line_count_after: usize,
28093}
28094
28095fn render_diff_hunk_controls(
28096 row: u32,
28097 status: &DiffHunkStatus,
28098 hunk_range: Range<Anchor>,
28099 is_created_file: bool,
28100 line_height: Pixels,
28101 editor: &Entity<Editor>,
28102 _window: &mut Window,
28103 cx: &mut App,
28104) -> AnyElement {
28105 h_flex()
28106 .h(line_height)
28107 .mr_1()
28108 .gap_1()
28109 .px_0p5()
28110 .pb_1()
28111 .border_x_1()
28112 .border_b_1()
28113 .border_color(cx.theme().colors().border_variant)
28114 .rounded_b_lg()
28115 .bg(cx.theme().colors().editor_background)
28116 .gap_1()
28117 .block_mouse_except_scroll()
28118 .shadow_md()
28119 .child(if status.has_secondary_hunk() {
28120 Button::new(("stage", row as u64), "Stage")
28121 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28122 .tooltip({
28123 let focus_handle = editor.focus_handle(cx);
28124 move |_window, cx| {
28125 Tooltip::for_action_in(
28126 "Stage Hunk",
28127 &::git::ToggleStaged,
28128 &focus_handle,
28129 cx,
28130 )
28131 }
28132 })
28133 .on_click({
28134 let editor = editor.clone();
28135 move |_event, _window, cx| {
28136 editor.update(cx, |editor, cx| {
28137 editor.stage_or_unstage_diff_hunks(
28138 true,
28139 vec![hunk_range.start..hunk_range.start],
28140 cx,
28141 );
28142 });
28143 }
28144 })
28145 } else {
28146 Button::new(("unstage", row as u64), "Unstage")
28147 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28148 .tooltip({
28149 let focus_handle = editor.focus_handle(cx);
28150 move |_window, cx| {
28151 Tooltip::for_action_in(
28152 "Unstage Hunk",
28153 &::git::ToggleStaged,
28154 &focus_handle,
28155 cx,
28156 )
28157 }
28158 })
28159 .on_click({
28160 let editor = editor.clone();
28161 move |_event, _window, cx| {
28162 editor.update(cx, |editor, cx| {
28163 editor.stage_or_unstage_diff_hunks(
28164 false,
28165 vec![hunk_range.start..hunk_range.start],
28166 cx,
28167 );
28168 });
28169 }
28170 })
28171 })
28172 .child(
28173 Button::new(("restore", row as u64), "Restore")
28174 .tooltip({
28175 let focus_handle = editor.focus_handle(cx);
28176 move |_window, cx| {
28177 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28178 }
28179 })
28180 .on_click({
28181 let editor = editor.clone();
28182 move |_event, window, cx| {
28183 editor.update(cx, |editor, cx| {
28184 let snapshot = editor.snapshot(window, cx);
28185 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28186 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28187 });
28188 }
28189 })
28190 .disabled(is_created_file),
28191 )
28192 .when(
28193 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28194 |el| {
28195 el.child(
28196 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28197 .shape(IconButtonShape::Square)
28198 .icon_size(IconSize::Small)
28199 // .disabled(!has_multiple_hunks)
28200 .tooltip({
28201 let focus_handle = editor.focus_handle(cx);
28202 move |_window, cx| {
28203 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28204 }
28205 })
28206 .on_click({
28207 let editor = editor.clone();
28208 move |_event, window, cx| {
28209 editor.update(cx, |editor, cx| {
28210 let snapshot = editor.snapshot(window, cx);
28211 let position =
28212 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28213 editor.go_to_hunk_before_or_after_position(
28214 &snapshot,
28215 position,
28216 Direction::Next,
28217 window,
28218 cx,
28219 );
28220 editor.expand_selected_diff_hunks(cx);
28221 });
28222 }
28223 }),
28224 )
28225 .child(
28226 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28227 .shape(IconButtonShape::Square)
28228 .icon_size(IconSize::Small)
28229 // .disabled(!has_multiple_hunks)
28230 .tooltip({
28231 let focus_handle = editor.focus_handle(cx);
28232 move |_window, cx| {
28233 Tooltip::for_action_in(
28234 "Previous Hunk",
28235 &GoToPreviousHunk,
28236 &focus_handle,
28237 cx,
28238 )
28239 }
28240 })
28241 .on_click({
28242 let editor = editor.clone();
28243 move |_event, window, cx| {
28244 editor.update(cx, |editor, cx| {
28245 let snapshot = editor.snapshot(window, cx);
28246 let point =
28247 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28248 editor.go_to_hunk_before_or_after_position(
28249 &snapshot,
28250 point,
28251 Direction::Prev,
28252 window,
28253 cx,
28254 );
28255 editor.expand_selected_diff_hunks(cx);
28256 });
28257 }
28258 }),
28259 )
28260 },
28261 )
28262 .into_any_element()
28263}
28264
28265pub fn multibuffer_context_lines(cx: &App) -> u32 {
28266 EditorSettings::try_get(cx)
28267 .map(|settings| settings.excerpt_context_lines)
28268 .unwrap_or(2)
28269 .min(32)
28270}