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, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 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)]
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 push_to_nav_history(
14917 &mut self,
14918 cursor_anchor: Anchor,
14919 new_position: Option<Point>,
14920 is_deactivate: bool,
14921 always: bool,
14922 cx: &mut Context<Self>,
14923 ) {
14924 if let Some(nav_history) = self.nav_history.as_mut() {
14925 let buffer = self.buffer.read(cx).read(cx);
14926 let cursor_position = cursor_anchor.to_point(&buffer);
14927 let scroll_state = self.scroll_manager.anchor();
14928 let scroll_top_row = scroll_state.top_row(&buffer);
14929 drop(buffer);
14930
14931 if let Some(new_position) = new_position {
14932 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14933 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14934 return;
14935 }
14936 }
14937
14938 nav_history.push(
14939 Some(NavigationData {
14940 cursor_anchor,
14941 cursor_position,
14942 scroll_anchor: scroll_state,
14943 scroll_top_row,
14944 }),
14945 cx,
14946 );
14947 cx.emit(EditorEvent::PushedToNavHistory {
14948 anchor: cursor_anchor,
14949 is_deactivate,
14950 })
14951 }
14952 }
14953
14954 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14955 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14956 let buffer = self.buffer.read(cx).snapshot(cx);
14957 let mut selection = self
14958 .selections
14959 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14960 selection.set_head(buffer.len(), SelectionGoal::None);
14961 self.change_selections(Default::default(), window, cx, |s| {
14962 s.select(vec![selection]);
14963 });
14964 }
14965
14966 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14968 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14969 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14970 });
14971 }
14972
14973 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14975 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14976 let mut selections = self.selections.all::<Point>(&display_map);
14977 let max_point = display_map.buffer_snapshot().max_point();
14978 for selection in &mut selections {
14979 let rows = selection.spanned_rows(true, &display_map);
14980 selection.start = Point::new(rows.start.0, 0);
14981 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14982 selection.reversed = false;
14983 }
14984 self.change_selections(Default::default(), window, cx, |s| {
14985 s.select(selections);
14986 });
14987 }
14988
14989 pub fn split_selection_into_lines(
14990 &mut self,
14991 action: &SplitSelectionIntoLines,
14992 window: &mut Window,
14993 cx: &mut Context<Self>,
14994 ) {
14995 let selections = self
14996 .selections
14997 .all::<Point>(&self.display_snapshot(cx))
14998 .into_iter()
14999 .map(|selection| selection.start..selection.end)
15000 .collect::<Vec<_>>();
15001 self.unfold_ranges(&selections, true, true, cx);
15002
15003 let mut new_selection_ranges = Vec::new();
15004 {
15005 let buffer = self.buffer.read(cx).read(cx);
15006 for selection in selections {
15007 for row in selection.start.row..selection.end.row {
15008 let line_start = Point::new(row, 0);
15009 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15010
15011 if action.keep_selections {
15012 // Keep the selection range for each line
15013 let selection_start = if row == selection.start.row {
15014 selection.start
15015 } else {
15016 line_start
15017 };
15018 new_selection_ranges.push(selection_start..line_end);
15019 } else {
15020 // Collapse to cursor at end of line
15021 new_selection_ranges.push(line_end..line_end);
15022 }
15023 }
15024
15025 let is_multiline_selection = selection.start.row != selection.end.row;
15026 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15027 // so this action feels more ergonomic when paired with other selection operations
15028 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15029 if !should_skip_last {
15030 if action.keep_selections {
15031 if is_multiline_selection {
15032 let line_start = Point::new(selection.end.row, 0);
15033 new_selection_ranges.push(line_start..selection.end);
15034 } else {
15035 new_selection_ranges.push(selection.start..selection.end);
15036 }
15037 } else {
15038 new_selection_ranges.push(selection.end..selection.end);
15039 }
15040 }
15041 }
15042 }
15043 self.change_selections(Default::default(), window, cx, |s| {
15044 s.select_ranges(new_selection_ranges);
15045 });
15046 }
15047
15048 pub fn add_selection_above(
15049 &mut self,
15050 action: &AddSelectionAbove,
15051 window: &mut Window,
15052 cx: &mut Context<Self>,
15053 ) {
15054 self.add_selection(true, action.skip_soft_wrap, window, cx);
15055 }
15056
15057 pub fn add_selection_below(
15058 &mut self,
15059 action: &AddSelectionBelow,
15060 window: &mut Window,
15061 cx: &mut Context<Self>,
15062 ) {
15063 self.add_selection(false, action.skip_soft_wrap, window, cx);
15064 }
15065
15066 fn add_selection(
15067 &mut self,
15068 above: bool,
15069 skip_soft_wrap: bool,
15070 window: &mut Window,
15071 cx: &mut Context<Self>,
15072 ) {
15073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15074
15075 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15076 let all_selections = self.selections.all::<Point>(&display_map);
15077 let text_layout_details = self.text_layout_details(window);
15078
15079 let (mut columnar_selections, new_selections_to_columnarize) = {
15080 if let Some(state) = self.add_selections_state.as_ref() {
15081 let columnar_selection_ids: HashSet<_> = state
15082 .groups
15083 .iter()
15084 .flat_map(|group| group.stack.iter())
15085 .copied()
15086 .collect();
15087
15088 all_selections
15089 .into_iter()
15090 .partition(|s| columnar_selection_ids.contains(&s.id))
15091 } else {
15092 (Vec::new(), all_selections)
15093 }
15094 };
15095
15096 let mut state = self
15097 .add_selections_state
15098 .take()
15099 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15100
15101 for selection in new_selections_to_columnarize {
15102 let range = selection.display_range(&display_map).sorted();
15103 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15104 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15105 let positions = start_x.min(end_x)..start_x.max(end_x);
15106 let mut stack = Vec::new();
15107 for row in range.start.row().0..=range.end.row().0 {
15108 if let Some(selection) = self.selections.build_columnar_selection(
15109 &display_map,
15110 DisplayRow(row),
15111 &positions,
15112 selection.reversed,
15113 &text_layout_details,
15114 ) {
15115 stack.push(selection.id);
15116 columnar_selections.push(selection);
15117 }
15118 }
15119 if !stack.is_empty() {
15120 if above {
15121 stack.reverse();
15122 }
15123 state.groups.push(AddSelectionsGroup { above, stack });
15124 }
15125 }
15126
15127 let mut final_selections = Vec::new();
15128 let end_row = if above {
15129 DisplayRow(0)
15130 } else {
15131 display_map.max_point().row()
15132 };
15133
15134 let mut last_added_item_per_group = HashMap::default();
15135 for group in state.groups.iter_mut() {
15136 if let Some(last_id) = group.stack.last() {
15137 last_added_item_per_group.insert(*last_id, group);
15138 }
15139 }
15140
15141 for selection in columnar_selections {
15142 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15143 if above == group.above {
15144 let range = selection.display_range(&display_map).sorted();
15145 debug_assert_eq!(range.start.row(), range.end.row());
15146 let mut row = range.start.row();
15147 let positions =
15148 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15149 Pixels::from(start)..Pixels::from(end)
15150 } else {
15151 let start_x =
15152 display_map.x_for_display_point(range.start, &text_layout_details);
15153 let end_x =
15154 display_map.x_for_display_point(range.end, &text_layout_details);
15155 start_x.min(end_x)..start_x.max(end_x)
15156 };
15157
15158 let mut maybe_new_selection = None;
15159 let direction = if above { -1 } else { 1 };
15160
15161 while row != end_row {
15162 let new_buffer_row = if skip_soft_wrap {
15163 let new_row = display_map
15164 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
15165 row = new_row.row();
15166 Some(new_row.to_point(&display_map).row)
15167 } else {
15168 if above {
15169 row.0 -= 1;
15170 } else {
15171 row.0 += 1;
15172 }
15173 None
15174 };
15175
15176 let new_selection = if let Some(buffer_row) = new_buffer_row {
15177 let start_col = selection.start.column;
15178 let end_col = selection.end.column;
15179 let buffer_columns = start_col.min(end_col)..start_col.max(end_col);
15180
15181 self.selections
15182 .build_columnar_selection_from_buffer_columns(
15183 &display_map,
15184 buffer_row,
15185 &buffer_columns,
15186 selection.reversed,
15187 &text_layout_details,
15188 )
15189 } else {
15190 self.selections.build_columnar_selection(
15191 &display_map,
15192 row,
15193 &positions,
15194 selection.reversed,
15195 &text_layout_details,
15196 )
15197 };
15198
15199 if let Some(new_selection) = new_selection {
15200 maybe_new_selection = Some(new_selection);
15201 break;
15202 }
15203 }
15204
15205 if let Some(new_selection) = maybe_new_selection {
15206 group.stack.push(new_selection.id);
15207 if above {
15208 final_selections.push(new_selection);
15209 final_selections.push(selection);
15210 } else {
15211 final_selections.push(selection);
15212 final_selections.push(new_selection);
15213 }
15214 } else {
15215 final_selections.push(selection);
15216 }
15217 } else {
15218 group.stack.pop();
15219 }
15220 } else {
15221 final_selections.push(selection);
15222 }
15223 }
15224
15225 self.change_selections(Default::default(), window, cx, |s| {
15226 s.select(final_selections);
15227 });
15228
15229 let final_selection_ids: HashSet<_> = self
15230 .selections
15231 .all::<Point>(&display_map)
15232 .iter()
15233 .map(|s| s.id)
15234 .collect();
15235 state.groups.retain_mut(|group| {
15236 // selections might get merged above so we remove invalid items from stacks
15237 group.stack.retain(|id| final_selection_ids.contains(id));
15238
15239 // single selection in stack can be treated as initial state
15240 group.stack.len() > 1
15241 });
15242
15243 if !state.groups.is_empty() {
15244 self.add_selections_state = Some(state);
15245 }
15246 }
15247
15248 pub fn insert_snippet_at_selections(
15249 &mut self,
15250 action: &InsertSnippet,
15251 window: &mut Window,
15252 cx: &mut Context<Self>,
15253 ) {
15254 self.try_insert_snippet_at_selections(action, window, cx)
15255 .log_err();
15256 }
15257
15258 fn try_insert_snippet_at_selections(
15259 &mut self,
15260 action: &InsertSnippet,
15261 window: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) -> Result<()> {
15264 let insertion_ranges = self
15265 .selections
15266 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15267 .into_iter()
15268 .map(|selection| selection.range())
15269 .collect_vec();
15270
15271 let snippet = if let Some(snippet_body) = &action.snippet {
15272 if action.language.is_none() && action.name.is_none() {
15273 Snippet::parse(snippet_body)?
15274 } else {
15275 bail!("`snippet` is mutually exclusive with `language` and `name`")
15276 }
15277 } else if let Some(name) = &action.name {
15278 let project = self.project().context("no project")?;
15279 let snippet_store = project.read(cx).snippets().read(cx);
15280 let snippet = snippet_store
15281 .snippets_for(action.language.clone(), cx)
15282 .into_iter()
15283 .find(|snippet| snippet.name == *name)
15284 .context("snippet not found")?;
15285 Snippet::parse(&snippet.body)?
15286 } else {
15287 // todo(andrew): open modal to select snippet
15288 bail!("`name` or `snippet` is required")
15289 };
15290
15291 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15292 }
15293
15294 fn select_match_ranges(
15295 &mut self,
15296 range: Range<MultiBufferOffset>,
15297 reversed: bool,
15298 replace_newest: bool,
15299 auto_scroll: Option<Autoscroll>,
15300 window: &mut Window,
15301 cx: &mut Context<Editor>,
15302 ) {
15303 self.unfold_ranges(
15304 std::slice::from_ref(&range),
15305 false,
15306 auto_scroll.is_some(),
15307 cx,
15308 );
15309 let effects = if let Some(scroll) = auto_scroll {
15310 SelectionEffects::scroll(scroll)
15311 } else {
15312 SelectionEffects::no_scroll()
15313 };
15314 self.change_selections(effects, window, cx, |s| {
15315 if replace_newest {
15316 s.delete(s.newest_anchor().id);
15317 }
15318 if reversed {
15319 s.insert_range(range.end..range.start);
15320 } else {
15321 s.insert_range(range);
15322 }
15323 });
15324 }
15325
15326 pub fn select_next_match_internal(
15327 &mut self,
15328 display_map: &DisplaySnapshot,
15329 replace_newest: bool,
15330 autoscroll: Option<Autoscroll>,
15331 window: &mut Window,
15332 cx: &mut Context<Self>,
15333 ) -> Result<()> {
15334 let buffer = display_map.buffer_snapshot();
15335 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15336 if let Some(mut select_next_state) = self.select_next_state.take() {
15337 let query = &select_next_state.query;
15338 if !select_next_state.done {
15339 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15340 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15341 let mut next_selected_range = None;
15342
15343 let bytes_after_last_selection =
15344 buffer.bytes_in_range(last_selection.end..buffer.len());
15345 let bytes_before_first_selection =
15346 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15347 let query_matches = query
15348 .stream_find_iter(bytes_after_last_selection)
15349 .map(|result| (last_selection.end, result))
15350 .chain(
15351 query
15352 .stream_find_iter(bytes_before_first_selection)
15353 .map(|result| (MultiBufferOffset(0), result)),
15354 );
15355
15356 for (start_offset, query_match) in query_matches {
15357 let query_match = query_match.unwrap(); // can only fail due to I/O
15358 let offset_range =
15359 start_offset + query_match.start()..start_offset + query_match.end();
15360
15361 if !select_next_state.wordwise
15362 || (!buffer.is_inside_word(offset_range.start, None)
15363 && !buffer.is_inside_word(offset_range.end, None))
15364 {
15365 let idx = selections
15366 .partition_point(|selection| selection.end <= offset_range.start);
15367 let overlaps = selections
15368 .get(idx)
15369 .map_or(false, |selection| selection.start < offset_range.end);
15370
15371 if !overlaps {
15372 next_selected_range = Some(offset_range);
15373 break;
15374 }
15375 }
15376 }
15377
15378 if let Some(next_selected_range) = next_selected_range {
15379 self.select_match_ranges(
15380 next_selected_range,
15381 last_selection.reversed,
15382 replace_newest,
15383 autoscroll,
15384 window,
15385 cx,
15386 );
15387 } else {
15388 select_next_state.done = true;
15389 }
15390 }
15391
15392 self.select_next_state = Some(select_next_state);
15393 } else {
15394 let mut only_carets = true;
15395 let mut same_text_selected = true;
15396 let mut selected_text = None;
15397
15398 let mut selections_iter = selections.iter().peekable();
15399 while let Some(selection) = selections_iter.next() {
15400 if selection.start != selection.end {
15401 only_carets = false;
15402 }
15403
15404 if same_text_selected {
15405 if selected_text.is_none() {
15406 selected_text =
15407 Some(buffer.text_for_range(selection.range()).collect::<String>());
15408 }
15409
15410 if let Some(next_selection) = selections_iter.peek() {
15411 if next_selection.len() == selection.len() {
15412 let next_selected_text = buffer
15413 .text_for_range(next_selection.range())
15414 .collect::<String>();
15415 if Some(next_selected_text) != selected_text {
15416 same_text_selected = false;
15417 selected_text = None;
15418 }
15419 } else {
15420 same_text_selected = false;
15421 selected_text = None;
15422 }
15423 }
15424 }
15425 }
15426
15427 if only_carets {
15428 for selection in &mut selections {
15429 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15430 selection.start = word_range.start;
15431 selection.end = word_range.end;
15432 selection.goal = SelectionGoal::None;
15433 selection.reversed = false;
15434 self.select_match_ranges(
15435 selection.start..selection.end,
15436 selection.reversed,
15437 replace_newest,
15438 autoscroll,
15439 window,
15440 cx,
15441 );
15442 }
15443
15444 if selections.len() == 1 {
15445 let selection = selections
15446 .last()
15447 .expect("ensured that there's only one selection");
15448 let query = buffer
15449 .text_for_range(selection.start..selection.end)
15450 .collect::<String>();
15451 let is_empty = query.is_empty();
15452 let select_state = SelectNextState {
15453 query: self.build_query(&[query], cx)?,
15454 wordwise: true,
15455 done: is_empty,
15456 };
15457 self.select_next_state = Some(select_state);
15458 } else {
15459 self.select_next_state = None;
15460 }
15461 } else if let Some(selected_text) = selected_text {
15462 self.select_next_state = Some(SelectNextState {
15463 query: self.build_query(&[selected_text], cx)?,
15464 wordwise: false,
15465 done: false,
15466 });
15467 self.select_next_match_internal(
15468 display_map,
15469 replace_newest,
15470 autoscroll,
15471 window,
15472 cx,
15473 )?;
15474 }
15475 }
15476 Ok(())
15477 }
15478
15479 pub fn select_all_matches(
15480 &mut self,
15481 _action: &SelectAllMatches,
15482 window: &mut Window,
15483 cx: &mut Context<Self>,
15484 ) -> Result<()> {
15485 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15486
15487 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15488
15489 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15490 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15491 else {
15492 return Ok(());
15493 };
15494
15495 let mut new_selections = Vec::new();
15496
15497 let reversed = self
15498 .selections
15499 .oldest::<MultiBufferOffset>(&display_map)
15500 .reversed;
15501 let buffer = display_map.buffer_snapshot();
15502 let query_matches = select_next_state
15503 .query
15504 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15505
15506 for query_match in query_matches.into_iter() {
15507 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15508 let offset_range = if reversed {
15509 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15510 } else {
15511 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15512 };
15513
15514 if !select_next_state.wordwise
15515 || (!buffer.is_inside_word(offset_range.start, None)
15516 && !buffer.is_inside_word(offset_range.end, None))
15517 {
15518 new_selections.push(offset_range.start..offset_range.end);
15519 }
15520 }
15521
15522 select_next_state.done = true;
15523
15524 if new_selections.is_empty() {
15525 log::error!("bug: new_selections is empty in select_all_matches");
15526 return Ok(());
15527 }
15528
15529 self.unfold_ranges(&new_selections, false, false, cx);
15530 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15531 selections.select_ranges(new_selections)
15532 });
15533
15534 Ok(())
15535 }
15536
15537 pub fn select_next(
15538 &mut self,
15539 action: &SelectNext,
15540 window: &mut Window,
15541 cx: &mut Context<Self>,
15542 ) -> Result<()> {
15543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15544 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15545 self.select_next_match_internal(
15546 &display_map,
15547 action.replace_newest,
15548 Some(Autoscroll::newest()),
15549 window,
15550 cx,
15551 )
15552 }
15553
15554 pub fn select_previous(
15555 &mut self,
15556 action: &SelectPrevious,
15557 window: &mut Window,
15558 cx: &mut Context<Self>,
15559 ) -> Result<()> {
15560 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15562 let buffer = display_map.buffer_snapshot();
15563 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15564 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15565 let query = &select_prev_state.query;
15566 if !select_prev_state.done {
15567 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15568 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15569 let mut next_selected_range = None;
15570 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15571 let bytes_before_last_selection =
15572 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15573 let bytes_after_first_selection =
15574 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15575 let query_matches = query
15576 .stream_find_iter(bytes_before_last_selection)
15577 .map(|result| (last_selection.start, result))
15578 .chain(
15579 query
15580 .stream_find_iter(bytes_after_first_selection)
15581 .map(|result| (buffer.len(), result)),
15582 );
15583 for (end_offset, query_match) in query_matches {
15584 let query_match = query_match.unwrap(); // can only fail due to I/O
15585 let offset_range =
15586 end_offset - query_match.end()..end_offset - query_match.start();
15587
15588 if !select_prev_state.wordwise
15589 || (!buffer.is_inside_word(offset_range.start, None)
15590 && !buffer.is_inside_word(offset_range.end, None))
15591 {
15592 next_selected_range = Some(offset_range);
15593 break;
15594 }
15595 }
15596
15597 if let Some(next_selected_range) = next_selected_range {
15598 self.select_match_ranges(
15599 next_selected_range,
15600 last_selection.reversed,
15601 action.replace_newest,
15602 Some(Autoscroll::newest()),
15603 window,
15604 cx,
15605 );
15606 } else {
15607 select_prev_state.done = true;
15608 }
15609 }
15610
15611 self.select_prev_state = Some(select_prev_state);
15612 } else {
15613 let mut only_carets = true;
15614 let mut same_text_selected = true;
15615 let mut selected_text = None;
15616
15617 let mut selections_iter = selections.iter().peekable();
15618 while let Some(selection) = selections_iter.next() {
15619 if selection.start != selection.end {
15620 only_carets = false;
15621 }
15622
15623 if same_text_selected {
15624 if selected_text.is_none() {
15625 selected_text =
15626 Some(buffer.text_for_range(selection.range()).collect::<String>());
15627 }
15628
15629 if let Some(next_selection) = selections_iter.peek() {
15630 if next_selection.len() == selection.len() {
15631 let next_selected_text = buffer
15632 .text_for_range(next_selection.range())
15633 .collect::<String>();
15634 if Some(next_selected_text) != selected_text {
15635 same_text_selected = false;
15636 selected_text = None;
15637 }
15638 } else {
15639 same_text_selected = false;
15640 selected_text = None;
15641 }
15642 }
15643 }
15644 }
15645
15646 if only_carets {
15647 for selection in &mut selections {
15648 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15649 selection.start = word_range.start;
15650 selection.end = word_range.end;
15651 selection.goal = SelectionGoal::None;
15652 selection.reversed = false;
15653 self.select_match_ranges(
15654 selection.start..selection.end,
15655 selection.reversed,
15656 action.replace_newest,
15657 Some(Autoscroll::newest()),
15658 window,
15659 cx,
15660 );
15661 }
15662 if selections.len() == 1 {
15663 let selection = selections
15664 .last()
15665 .expect("ensured that there's only one selection");
15666 let query = buffer
15667 .text_for_range(selection.start..selection.end)
15668 .collect::<String>();
15669 let is_empty = query.is_empty();
15670 let select_state = SelectNextState {
15671 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15672 wordwise: true,
15673 done: is_empty,
15674 };
15675 self.select_prev_state = Some(select_state);
15676 } else {
15677 self.select_prev_state = None;
15678 }
15679 } else if let Some(selected_text) = selected_text {
15680 self.select_prev_state = Some(SelectNextState {
15681 query: self
15682 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15683 wordwise: false,
15684 done: false,
15685 });
15686 self.select_previous(action, window, cx)?;
15687 }
15688 }
15689 Ok(())
15690 }
15691
15692 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15693 /// setting the case sensitivity based on the global
15694 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15695 /// editor's settings.
15696 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15697 where
15698 I: IntoIterator<Item = P>,
15699 P: AsRef<[u8]>,
15700 {
15701 let case_sensitive = self
15702 .select_next_is_case_sensitive
15703 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15704
15705 let mut builder = AhoCorasickBuilder::new();
15706 builder.ascii_case_insensitive(!case_sensitive);
15707 builder.build(patterns)
15708 }
15709
15710 pub fn find_next_match(
15711 &mut self,
15712 _: &FindNextMatch,
15713 window: &mut Window,
15714 cx: &mut Context<Self>,
15715 ) -> Result<()> {
15716 let selections = self.selections.disjoint_anchors_arc();
15717 match selections.first() {
15718 Some(first) if selections.len() >= 2 => {
15719 self.change_selections(Default::default(), window, cx, |s| {
15720 s.select_ranges([first.range()]);
15721 });
15722 }
15723 _ => self.select_next(
15724 &SelectNext {
15725 replace_newest: true,
15726 },
15727 window,
15728 cx,
15729 )?,
15730 }
15731 Ok(())
15732 }
15733
15734 pub fn find_previous_match(
15735 &mut self,
15736 _: &FindPreviousMatch,
15737 window: &mut Window,
15738 cx: &mut Context<Self>,
15739 ) -> Result<()> {
15740 let selections = self.selections.disjoint_anchors_arc();
15741 match selections.last() {
15742 Some(last) if selections.len() >= 2 => {
15743 self.change_selections(Default::default(), window, cx, |s| {
15744 s.select_ranges([last.range()]);
15745 });
15746 }
15747 _ => self.select_previous(
15748 &SelectPrevious {
15749 replace_newest: true,
15750 },
15751 window,
15752 cx,
15753 )?,
15754 }
15755 Ok(())
15756 }
15757
15758 pub fn toggle_comments(
15759 &mut self,
15760 action: &ToggleComments,
15761 window: &mut Window,
15762 cx: &mut Context<Self>,
15763 ) {
15764 if self.read_only(cx) {
15765 return;
15766 }
15767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15768 let text_layout_details = &self.text_layout_details(window);
15769 self.transact(window, cx, |this, window, cx| {
15770 let mut selections = this
15771 .selections
15772 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15773 let mut edits = Vec::new();
15774 let mut selection_edit_ranges = Vec::new();
15775 let mut last_toggled_row = None;
15776 let snapshot = this.buffer.read(cx).read(cx);
15777 let empty_str: Arc<str> = Arc::default();
15778 let mut suffixes_inserted = Vec::new();
15779 let ignore_indent = action.ignore_indent;
15780
15781 fn comment_prefix_range(
15782 snapshot: &MultiBufferSnapshot,
15783 row: MultiBufferRow,
15784 comment_prefix: &str,
15785 comment_prefix_whitespace: &str,
15786 ignore_indent: bool,
15787 ) -> Range<Point> {
15788 let indent_size = if ignore_indent {
15789 0
15790 } else {
15791 snapshot.indent_size_for_line(row).len
15792 };
15793
15794 let start = Point::new(row.0, indent_size);
15795
15796 let mut line_bytes = snapshot
15797 .bytes_in_range(start..snapshot.max_point())
15798 .flatten()
15799 .copied();
15800
15801 // If this line currently begins with the line comment prefix, then record
15802 // the range containing the prefix.
15803 if line_bytes
15804 .by_ref()
15805 .take(comment_prefix.len())
15806 .eq(comment_prefix.bytes())
15807 {
15808 // Include any whitespace that matches the comment prefix.
15809 let matching_whitespace_len = line_bytes
15810 .zip(comment_prefix_whitespace.bytes())
15811 .take_while(|(a, b)| a == b)
15812 .count() as u32;
15813 let end = Point::new(
15814 start.row,
15815 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15816 );
15817 start..end
15818 } else {
15819 start..start
15820 }
15821 }
15822
15823 fn comment_suffix_range(
15824 snapshot: &MultiBufferSnapshot,
15825 row: MultiBufferRow,
15826 comment_suffix: &str,
15827 comment_suffix_has_leading_space: bool,
15828 ) -> Range<Point> {
15829 let end = Point::new(row.0, snapshot.line_len(row));
15830 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15831
15832 let mut line_end_bytes = snapshot
15833 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15834 .flatten()
15835 .copied();
15836
15837 let leading_space_len = if suffix_start_column > 0
15838 && line_end_bytes.next() == Some(b' ')
15839 && comment_suffix_has_leading_space
15840 {
15841 1
15842 } else {
15843 0
15844 };
15845
15846 // If this line currently begins with the line comment prefix, then record
15847 // the range containing the prefix.
15848 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15849 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15850 start..end
15851 } else {
15852 end..end
15853 }
15854 }
15855
15856 // TODO: Handle selections that cross excerpts
15857 for selection in &mut selections {
15858 let start_column = snapshot
15859 .indent_size_for_line(MultiBufferRow(selection.start.row))
15860 .len;
15861 let language = if let Some(language) =
15862 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15863 {
15864 language
15865 } else {
15866 continue;
15867 };
15868
15869 selection_edit_ranges.clear();
15870
15871 // If multiple selections contain a given row, avoid processing that
15872 // row more than once.
15873 let mut start_row = MultiBufferRow(selection.start.row);
15874 if last_toggled_row == Some(start_row) {
15875 start_row = start_row.next_row();
15876 }
15877 let end_row =
15878 if selection.end.row > selection.start.row && selection.end.column == 0 {
15879 MultiBufferRow(selection.end.row - 1)
15880 } else {
15881 MultiBufferRow(selection.end.row)
15882 };
15883 last_toggled_row = Some(end_row);
15884
15885 if start_row > end_row {
15886 continue;
15887 }
15888
15889 // If the language has line comments, toggle those.
15890 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15891
15892 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15893 if ignore_indent {
15894 full_comment_prefixes = full_comment_prefixes
15895 .into_iter()
15896 .map(|s| Arc::from(s.trim_end()))
15897 .collect();
15898 }
15899
15900 if !full_comment_prefixes.is_empty() {
15901 let first_prefix = full_comment_prefixes
15902 .first()
15903 .expect("prefixes is non-empty");
15904 let prefix_trimmed_lengths = full_comment_prefixes
15905 .iter()
15906 .map(|p| p.trim_end_matches(' ').len())
15907 .collect::<SmallVec<[usize; 4]>>();
15908
15909 let mut all_selection_lines_are_comments = true;
15910
15911 for row in start_row.0..=end_row.0 {
15912 let row = MultiBufferRow(row);
15913 if start_row < end_row && snapshot.is_line_blank(row) {
15914 continue;
15915 }
15916
15917 let prefix_range = full_comment_prefixes
15918 .iter()
15919 .zip(prefix_trimmed_lengths.iter().copied())
15920 .map(|(prefix, trimmed_prefix_len)| {
15921 comment_prefix_range(
15922 snapshot.deref(),
15923 row,
15924 &prefix[..trimmed_prefix_len],
15925 &prefix[trimmed_prefix_len..],
15926 ignore_indent,
15927 )
15928 })
15929 .max_by_key(|range| range.end.column - range.start.column)
15930 .expect("prefixes is non-empty");
15931
15932 if prefix_range.is_empty() {
15933 all_selection_lines_are_comments = false;
15934 }
15935
15936 selection_edit_ranges.push(prefix_range);
15937 }
15938
15939 if all_selection_lines_are_comments {
15940 edits.extend(
15941 selection_edit_ranges
15942 .iter()
15943 .cloned()
15944 .map(|range| (range, empty_str.clone())),
15945 );
15946 } else {
15947 let min_column = selection_edit_ranges
15948 .iter()
15949 .map(|range| range.start.column)
15950 .min()
15951 .unwrap_or(0);
15952 edits.extend(selection_edit_ranges.iter().map(|range| {
15953 let position = Point::new(range.start.row, min_column);
15954 (position..position, first_prefix.clone())
15955 }));
15956 }
15957 } else if let Some(BlockCommentConfig {
15958 start: full_comment_prefix,
15959 end: comment_suffix,
15960 ..
15961 }) = language.block_comment()
15962 {
15963 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15964 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15965 let prefix_range = comment_prefix_range(
15966 snapshot.deref(),
15967 start_row,
15968 comment_prefix,
15969 comment_prefix_whitespace,
15970 ignore_indent,
15971 );
15972 let suffix_range = comment_suffix_range(
15973 snapshot.deref(),
15974 end_row,
15975 comment_suffix.trim_start_matches(' '),
15976 comment_suffix.starts_with(' '),
15977 );
15978
15979 if prefix_range.is_empty() || suffix_range.is_empty() {
15980 edits.push((
15981 prefix_range.start..prefix_range.start,
15982 full_comment_prefix.clone(),
15983 ));
15984 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15985 suffixes_inserted.push((end_row, comment_suffix.len()));
15986 } else {
15987 edits.push((prefix_range, empty_str.clone()));
15988 edits.push((suffix_range, empty_str.clone()));
15989 }
15990 } else {
15991 continue;
15992 }
15993 }
15994
15995 drop(snapshot);
15996 this.buffer.update(cx, |buffer, cx| {
15997 buffer.edit(edits, None, cx);
15998 });
15999
16000 // Adjust selections so that they end before any comment suffixes that
16001 // were inserted.
16002 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16003 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16004 let snapshot = this.buffer.read(cx).read(cx);
16005 for selection in &mut selections {
16006 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16007 match row.cmp(&MultiBufferRow(selection.end.row)) {
16008 Ordering::Less => {
16009 suffixes_inserted.next();
16010 continue;
16011 }
16012 Ordering::Greater => break,
16013 Ordering::Equal => {
16014 if selection.end.column == snapshot.line_len(row) {
16015 if selection.is_empty() {
16016 selection.start.column -= suffix_len as u32;
16017 }
16018 selection.end.column -= suffix_len as u32;
16019 }
16020 break;
16021 }
16022 }
16023 }
16024 }
16025
16026 drop(snapshot);
16027 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16028
16029 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16030 let selections_on_single_row = selections.windows(2).all(|selections| {
16031 selections[0].start.row == selections[1].start.row
16032 && selections[0].end.row == selections[1].end.row
16033 && selections[0].start.row == selections[0].end.row
16034 });
16035 let selections_selecting = selections
16036 .iter()
16037 .any(|selection| selection.start != selection.end);
16038 let advance_downwards = action.advance_downwards
16039 && selections_on_single_row
16040 && !selections_selecting
16041 && !matches!(this.mode, EditorMode::SingleLine);
16042
16043 if advance_downwards {
16044 let snapshot = this.buffer.read(cx).snapshot(cx);
16045
16046 this.change_selections(Default::default(), window, cx, |s| {
16047 s.move_cursors_with(|display_snapshot, display_point, _| {
16048 let mut point = display_point.to_point(display_snapshot);
16049 point.row += 1;
16050 point = snapshot.clip_point(point, Bias::Left);
16051 let display_point = point.to_display_point(display_snapshot);
16052 let goal = SelectionGoal::HorizontalPosition(
16053 display_snapshot
16054 .x_for_display_point(display_point, text_layout_details)
16055 .into(),
16056 );
16057 (display_point, goal)
16058 })
16059 });
16060 }
16061 });
16062 }
16063
16064 pub fn select_enclosing_symbol(
16065 &mut self,
16066 _: &SelectEnclosingSymbol,
16067 window: &mut Window,
16068 cx: &mut Context<Self>,
16069 ) {
16070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16071
16072 let buffer = self.buffer.read(cx).snapshot(cx);
16073 let old_selections = self
16074 .selections
16075 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16076 .into_boxed_slice();
16077
16078 fn update_selection(
16079 selection: &Selection<MultiBufferOffset>,
16080 buffer_snap: &MultiBufferSnapshot,
16081 ) -> Option<Selection<MultiBufferOffset>> {
16082 let cursor = selection.head();
16083 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16084 for symbol in symbols.iter().rev() {
16085 let start = symbol.range.start.to_offset(buffer_snap);
16086 let end = symbol.range.end.to_offset(buffer_snap);
16087 let new_range = start..end;
16088 if start < selection.start || end > selection.end {
16089 return Some(Selection {
16090 id: selection.id,
16091 start: new_range.start,
16092 end: new_range.end,
16093 goal: SelectionGoal::None,
16094 reversed: selection.reversed,
16095 });
16096 }
16097 }
16098 None
16099 }
16100
16101 let mut selected_larger_symbol = false;
16102 let new_selections = old_selections
16103 .iter()
16104 .map(|selection| match update_selection(selection, &buffer) {
16105 Some(new_selection) => {
16106 if new_selection.range() != selection.range() {
16107 selected_larger_symbol = true;
16108 }
16109 new_selection
16110 }
16111 None => selection.clone(),
16112 })
16113 .collect::<Vec<_>>();
16114
16115 if selected_larger_symbol {
16116 self.change_selections(Default::default(), window, cx, |s| {
16117 s.select(new_selections);
16118 });
16119 }
16120 }
16121
16122 pub fn select_larger_syntax_node(
16123 &mut self,
16124 _: &SelectLargerSyntaxNode,
16125 window: &mut Window,
16126 cx: &mut Context<Self>,
16127 ) {
16128 let Some(visible_row_count) = self.visible_row_count() else {
16129 return;
16130 };
16131 let old_selections: Box<[_]> = self
16132 .selections
16133 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16134 .into();
16135 if old_selections.is_empty() {
16136 return;
16137 }
16138
16139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16140
16141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16142 let buffer = self.buffer.read(cx).snapshot(cx);
16143
16144 let mut selected_larger_node = false;
16145 let mut new_selections = old_selections
16146 .iter()
16147 .map(|selection| {
16148 let old_range = selection.start..selection.end;
16149
16150 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16151 // manually select word at selection
16152 if ["string_content", "inline"].contains(&node.kind()) {
16153 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16154 // ignore if word is already selected
16155 if !word_range.is_empty() && old_range != word_range {
16156 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16157 // only select word if start and end point belongs to same word
16158 if word_range == last_word_range {
16159 selected_larger_node = true;
16160 return Selection {
16161 id: selection.id,
16162 start: word_range.start,
16163 end: word_range.end,
16164 goal: SelectionGoal::None,
16165 reversed: selection.reversed,
16166 };
16167 }
16168 }
16169 }
16170 }
16171
16172 let mut new_range = old_range.clone();
16173 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16174 new_range = range;
16175 if !node.is_named() {
16176 continue;
16177 }
16178 if !display_map.intersects_fold(new_range.start)
16179 && !display_map.intersects_fold(new_range.end)
16180 {
16181 break;
16182 }
16183 }
16184
16185 selected_larger_node |= new_range != old_range;
16186 Selection {
16187 id: selection.id,
16188 start: new_range.start,
16189 end: new_range.end,
16190 goal: SelectionGoal::None,
16191 reversed: selection.reversed,
16192 }
16193 })
16194 .collect::<Vec<_>>();
16195
16196 if !selected_larger_node {
16197 return; // don't put this call in the history
16198 }
16199
16200 // scroll based on transformation done to the last selection created by the user
16201 let (last_old, last_new) = old_selections
16202 .last()
16203 .zip(new_selections.last().cloned())
16204 .expect("old_selections isn't empty");
16205
16206 // revert selection
16207 let is_selection_reversed = {
16208 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16209 new_selections.last_mut().expect("checked above").reversed =
16210 should_newest_selection_be_reversed;
16211 should_newest_selection_be_reversed
16212 };
16213
16214 if selected_larger_node {
16215 self.select_syntax_node_history.disable_clearing = true;
16216 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16217 s.select(new_selections.clone());
16218 });
16219 self.select_syntax_node_history.disable_clearing = false;
16220 }
16221
16222 let start_row = last_new.start.to_display_point(&display_map).row().0;
16223 let end_row = last_new.end.to_display_point(&display_map).row().0;
16224 let selection_height = end_row - start_row + 1;
16225 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16226
16227 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16228 let scroll_behavior = if fits_on_the_screen {
16229 self.request_autoscroll(Autoscroll::fit(), cx);
16230 SelectSyntaxNodeScrollBehavior::FitSelection
16231 } else if is_selection_reversed {
16232 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16233 SelectSyntaxNodeScrollBehavior::CursorTop
16234 } else {
16235 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16236 SelectSyntaxNodeScrollBehavior::CursorBottom
16237 };
16238
16239 self.select_syntax_node_history.push((
16240 old_selections,
16241 scroll_behavior,
16242 is_selection_reversed,
16243 ));
16244 }
16245
16246 pub fn select_smaller_syntax_node(
16247 &mut self,
16248 _: &SelectSmallerSyntaxNode,
16249 window: &mut Window,
16250 cx: &mut Context<Self>,
16251 ) {
16252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16253
16254 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16255 self.select_syntax_node_history.pop()
16256 {
16257 if let Some(selection) = selections.last_mut() {
16258 selection.reversed = is_selection_reversed;
16259 }
16260
16261 self.select_syntax_node_history.disable_clearing = true;
16262 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16263 s.select(selections.to_vec());
16264 });
16265 self.select_syntax_node_history.disable_clearing = false;
16266
16267 match scroll_behavior {
16268 SelectSyntaxNodeScrollBehavior::CursorTop => {
16269 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16270 }
16271 SelectSyntaxNodeScrollBehavior::FitSelection => {
16272 self.request_autoscroll(Autoscroll::fit(), cx);
16273 }
16274 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16275 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16276 }
16277 }
16278 }
16279 }
16280
16281 pub fn unwrap_syntax_node(
16282 &mut self,
16283 _: &UnwrapSyntaxNode,
16284 window: &mut Window,
16285 cx: &mut Context<Self>,
16286 ) {
16287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16288
16289 let buffer = self.buffer.read(cx).snapshot(cx);
16290 let selections = self
16291 .selections
16292 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16293 .into_iter()
16294 // subtracting the offset requires sorting
16295 .sorted_by_key(|i| i.start);
16296
16297 let full_edits = selections
16298 .into_iter()
16299 .filter_map(|selection| {
16300 let child = if selection.is_empty()
16301 && let Some((_, ancestor_range)) =
16302 buffer.syntax_ancestor(selection.start..selection.end)
16303 {
16304 ancestor_range
16305 } else {
16306 selection.range()
16307 };
16308
16309 let mut parent = child.clone();
16310 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16311 parent = ancestor_range;
16312 if parent.start < child.start || parent.end > child.end {
16313 break;
16314 }
16315 }
16316
16317 if parent == child {
16318 return None;
16319 }
16320 let text = buffer.text_for_range(child).collect::<String>();
16321 Some((selection.id, parent, text))
16322 })
16323 .collect::<Vec<_>>();
16324 if full_edits.is_empty() {
16325 return;
16326 }
16327
16328 self.transact(window, cx, |this, window, cx| {
16329 this.buffer.update(cx, |buffer, cx| {
16330 buffer.edit(
16331 full_edits
16332 .iter()
16333 .map(|(_, p, t)| (p.clone(), t.clone()))
16334 .collect::<Vec<_>>(),
16335 None,
16336 cx,
16337 );
16338 });
16339 this.change_selections(Default::default(), window, cx, |s| {
16340 let mut offset = 0;
16341 let mut selections = vec![];
16342 for (id, parent, text) in full_edits {
16343 let start = parent.start - offset;
16344 offset += (parent.end - parent.start) - text.len();
16345 selections.push(Selection {
16346 id,
16347 start,
16348 end: start + text.len(),
16349 reversed: false,
16350 goal: Default::default(),
16351 });
16352 }
16353 s.select(selections);
16354 });
16355 });
16356 }
16357
16358 pub fn select_next_syntax_node(
16359 &mut self,
16360 _: &SelectNextSyntaxNode,
16361 window: &mut Window,
16362 cx: &mut Context<Self>,
16363 ) {
16364 let old_selections: Box<[_]> = self
16365 .selections
16366 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16367 .into();
16368 if old_selections.is_empty() {
16369 return;
16370 }
16371
16372 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16373
16374 let buffer = self.buffer.read(cx).snapshot(cx);
16375 let mut selected_sibling = false;
16376
16377 let new_selections = old_selections
16378 .iter()
16379 .map(|selection| {
16380 let old_range = selection.start..selection.end;
16381
16382 let old_range =
16383 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16384 let excerpt = buffer.excerpt_containing(old_range.clone());
16385
16386 if let Some(mut excerpt) = excerpt
16387 && let Some(node) = excerpt
16388 .buffer()
16389 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16390 {
16391 let new_range = excerpt.map_range_from_buffer(
16392 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16393 );
16394 selected_sibling = true;
16395 Selection {
16396 id: selection.id,
16397 start: new_range.start,
16398 end: new_range.end,
16399 goal: SelectionGoal::None,
16400 reversed: selection.reversed,
16401 }
16402 } else {
16403 selection.clone()
16404 }
16405 })
16406 .collect::<Vec<_>>();
16407
16408 if selected_sibling {
16409 self.change_selections(
16410 SelectionEffects::scroll(Autoscroll::fit()),
16411 window,
16412 cx,
16413 |s| {
16414 s.select(new_selections);
16415 },
16416 );
16417 }
16418 }
16419
16420 pub fn select_prev_syntax_node(
16421 &mut self,
16422 _: &SelectPreviousSyntaxNode,
16423 window: &mut Window,
16424 cx: &mut Context<Self>,
16425 ) {
16426 let old_selections: Box<[_]> = self
16427 .selections
16428 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16429 .into();
16430 if old_selections.is_empty() {
16431 return;
16432 }
16433
16434 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16435
16436 let buffer = self.buffer.read(cx).snapshot(cx);
16437 let mut selected_sibling = false;
16438
16439 let new_selections = old_selections
16440 .iter()
16441 .map(|selection| {
16442 let old_range = selection.start..selection.end;
16443 let old_range =
16444 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16445 let excerpt = buffer.excerpt_containing(old_range.clone());
16446
16447 if let Some(mut excerpt) = excerpt
16448 && let Some(node) = excerpt
16449 .buffer()
16450 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16451 {
16452 let new_range = excerpt.map_range_from_buffer(
16453 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16454 );
16455 selected_sibling = true;
16456 Selection {
16457 id: selection.id,
16458 start: new_range.start,
16459 end: new_range.end,
16460 goal: SelectionGoal::None,
16461 reversed: selection.reversed,
16462 }
16463 } else {
16464 selection.clone()
16465 }
16466 })
16467 .collect::<Vec<_>>();
16468
16469 if selected_sibling {
16470 self.change_selections(
16471 SelectionEffects::scroll(Autoscroll::fit()),
16472 window,
16473 cx,
16474 |s| {
16475 s.select(new_selections);
16476 },
16477 );
16478 }
16479 }
16480
16481 pub fn move_to_start_of_larger_syntax_node(
16482 &mut self,
16483 _: &MoveToStartOfLargerSyntaxNode,
16484 window: &mut Window,
16485 cx: &mut Context<Self>,
16486 ) {
16487 self.move_cursors_to_syntax_nodes(window, cx, false);
16488 }
16489
16490 pub fn move_to_end_of_larger_syntax_node(
16491 &mut self,
16492 _: &MoveToEndOfLargerSyntaxNode,
16493 window: &mut Window,
16494 cx: &mut Context<Self>,
16495 ) {
16496 self.move_cursors_to_syntax_nodes(window, cx, true);
16497 }
16498
16499 fn move_cursors_to_syntax_nodes(
16500 &mut self,
16501 window: &mut Window,
16502 cx: &mut Context<Self>,
16503 move_to_end: bool,
16504 ) -> bool {
16505 let old_selections: Box<[_]> = self
16506 .selections
16507 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16508 .into();
16509 if old_selections.is_empty() {
16510 return false;
16511 }
16512
16513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16514
16515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16516 let buffer = self.buffer.read(cx).snapshot(cx);
16517
16518 let mut any_cursor_moved = false;
16519 let new_selections = old_selections
16520 .iter()
16521 .map(|selection| {
16522 if !selection.is_empty() {
16523 return selection.clone();
16524 }
16525
16526 let selection_pos = selection.head();
16527 let old_range = selection_pos..selection_pos;
16528
16529 let mut new_pos = selection_pos;
16530 let mut search_range = old_range;
16531 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16532 search_range = range.clone();
16533 if !node.is_named()
16534 || display_map.intersects_fold(range.start)
16535 || display_map.intersects_fold(range.end)
16536 // If cursor is already at the end of the syntax node, continue searching
16537 || (move_to_end && range.end == selection_pos)
16538 // If cursor is already at the start of the syntax node, continue searching
16539 || (!move_to_end && range.start == selection_pos)
16540 {
16541 continue;
16542 }
16543
16544 // If we found a string_content node, find the largest parent that is still string_content
16545 // Enables us to skip to the end of strings without taking multiple steps inside the string
16546 let (_, final_range) = if node.kind() == "string_content" {
16547 let mut current_node = node;
16548 let mut current_range = range;
16549 while let Some((parent, parent_range)) =
16550 buffer.syntax_ancestor(current_range.clone())
16551 {
16552 if parent.kind() == "string_content" {
16553 current_node = parent;
16554 current_range = parent_range;
16555 } else {
16556 break;
16557 }
16558 }
16559
16560 (current_node, current_range)
16561 } else {
16562 (node, range)
16563 };
16564
16565 new_pos = if move_to_end {
16566 final_range.end
16567 } else {
16568 final_range.start
16569 };
16570
16571 break;
16572 }
16573
16574 any_cursor_moved |= new_pos != selection_pos;
16575
16576 Selection {
16577 id: selection.id,
16578 start: new_pos,
16579 end: new_pos,
16580 goal: SelectionGoal::None,
16581 reversed: false,
16582 }
16583 })
16584 .collect::<Vec<_>>();
16585
16586 self.change_selections(Default::default(), window, cx, |s| {
16587 s.select(new_selections);
16588 });
16589 self.request_autoscroll(Autoscroll::newest(), cx);
16590
16591 any_cursor_moved
16592 }
16593
16594 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16595 if !EditorSettings::get_global(cx).gutter.runnables {
16596 self.clear_tasks();
16597 return Task::ready(());
16598 }
16599 let project = self.project().map(Entity::downgrade);
16600 let task_sources = self.lsp_task_sources(cx);
16601 let multi_buffer = self.buffer.downgrade();
16602 cx.spawn_in(window, async move |editor, cx| {
16603 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16604 let Some(project) = project.and_then(|p| p.upgrade()) else {
16605 return;
16606 };
16607 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16608 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16609 }) else {
16610 return;
16611 };
16612
16613 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16614 if hide_runnables {
16615 return;
16616 }
16617 let new_rows =
16618 cx.background_spawn({
16619 let snapshot = display_snapshot.clone();
16620 async move {
16621 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16622 }
16623 })
16624 .await;
16625 let Ok(lsp_tasks) =
16626 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16627 else {
16628 return;
16629 };
16630 let lsp_tasks = lsp_tasks.await;
16631
16632 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16633 lsp_tasks
16634 .into_iter()
16635 .flat_map(|(kind, tasks)| {
16636 tasks.into_iter().filter_map(move |(location, task)| {
16637 Some((kind.clone(), location?, task))
16638 })
16639 })
16640 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16641 let buffer = location.target.buffer;
16642 let buffer_snapshot = buffer.read(cx).snapshot();
16643 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16644 |(excerpt_id, snapshot, _)| {
16645 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16646 display_snapshot
16647 .buffer_snapshot()
16648 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16649 } else {
16650 None
16651 }
16652 },
16653 );
16654 if let Some(offset) = offset {
16655 let task_buffer_range =
16656 location.target.range.to_point(&buffer_snapshot);
16657 let context_buffer_range =
16658 task_buffer_range.to_offset(&buffer_snapshot);
16659 let context_range = BufferOffset(context_buffer_range.start)
16660 ..BufferOffset(context_buffer_range.end);
16661
16662 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16663 .or_insert_with(|| RunnableTasks {
16664 templates: Vec::new(),
16665 offset,
16666 column: task_buffer_range.start.column,
16667 extra_variables: HashMap::default(),
16668 context_range,
16669 })
16670 .templates
16671 .push((kind, task.original_task().clone()));
16672 }
16673
16674 acc
16675 })
16676 }) else {
16677 return;
16678 };
16679
16680 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16681 buffer.language_settings(cx).tasks.prefer_lsp
16682 }) else {
16683 return;
16684 };
16685
16686 let rows = Self::runnable_rows(
16687 project,
16688 display_snapshot,
16689 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16690 new_rows,
16691 cx.clone(),
16692 )
16693 .await;
16694 editor
16695 .update(cx, |editor, _| {
16696 editor.clear_tasks();
16697 for (key, mut value) in rows {
16698 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16699 value.templates.extend(lsp_tasks.templates);
16700 }
16701
16702 editor.insert_tasks(key, value);
16703 }
16704 for (key, value) in lsp_tasks_by_rows {
16705 editor.insert_tasks(key, value);
16706 }
16707 })
16708 .ok();
16709 })
16710 }
16711 fn fetch_runnable_ranges(
16712 snapshot: &DisplaySnapshot,
16713 range: Range<Anchor>,
16714 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16715 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16716 }
16717
16718 fn runnable_rows(
16719 project: Entity<Project>,
16720 snapshot: DisplaySnapshot,
16721 prefer_lsp: bool,
16722 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16723 cx: AsyncWindowContext,
16724 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16725 cx.spawn(async move |cx| {
16726 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16727 for (run_range, mut runnable) in runnable_ranges {
16728 let Some(tasks) = cx
16729 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16730 .ok()
16731 else {
16732 continue;
16733 };
16734 let mut tasks = tasks.await;
16735
16736 if prefer_lsp {
16737 tasks.retain(|(task_kind, _)| {
16738 !matches!(task_kind, TaskSourceKind::Language { .. })
16739 });
16740 }
16741 if tasks.is_empty() {
16742 continue;
16743 }
16744
16745 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16746 let Some(row) = snapshot
16747 .buffer_snapshot()
16748 .buffer_line_for_row(MultiBufferRow(point.row))
16749 .map(|(_, range)| range.start.row)
16750 else {
16751 continue;
16752 };
16753
16754 let context_range =
16755 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16756 runnable_rows.push((
16757 (runnable.buffer_id, row),
16758 RunnableTasks {
16759 templates: tasks,
16760 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16761 context_range,
16762 column: point.column,
16763 extra_variables: runnable.extra_captures,
16764 },
16765 ));
16766 }
16767 runnable_rows
16768 })
16769 }
16770
16771 fn templates_with_tags(
16772 project: &Entity<Project>,
16773 runnable: &mut Runnable,
16774 cx: &mut App,
16775 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16776 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16777 let (worktree_id, file) = project
16778 .buffer_for_id(runnable.buffer, cx)
16779 .and_then(|buffer| buffer.read(cx).file())
16780 .map(|file| (file.worktree_id(cx), file.clone()))
16781 .unzip();
16782
16783 (
16784 project.task_store().read(cx).task_inventory().cloned(),
16785 worktree_id,
16786 file,
16787 )
16788 });
16789
16790 let tags = mem::take(&mut runnable.tags);
16791 let language = runnable.language.clone();
16792 cx.spawn(async move |cx| {
16793 let mut templates_with_tags = Vec::new();
16794 if let Some(inventory) = inventory {
16795 for RunnableTag(tag) in tags {
16796 let new_tasks = inventory.update(cx, |inventory, cx| {
16797 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16798 });
16799 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16800 move |(_, template)| {
16801 template.tags.iter().any(|source_tag| source_tag == &tag)
16802 },
16803 ));
16804 }
16805 }
16806 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16807
16808 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16809 // Strongest source wins; if we have worktree tag binding, prefer that to
16810 // global and language bindings;
16811 // if we have a global binding, prefer that to language binding.
16812 let first_mismatch = templates_with_tags
16813 .iter()
16814 .position(|(tag_source, _)| tag_source != leading_tag_source);
16815 if let Some(index) = first_mismatch {
16816 templates_with_tags.truncate(index);
16817 }
16818 }
16819
16820 templates_with_tags
16821 })
16822 }
16823
16824 pub fn move_to_enclosing_bracket(
16825 &mut self,
16826 _: &MoveToEnclosingBracket,
16827 window: &mut Window,
16828 cx: &mut Context<Self>,
16829 ) {
16830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16831 self.change_selections(Default::default(), window, cx, |s| {
16832 s.move_offsets_with(|snapshot, selection| {
16833 let Some(enclosing_bracket_ranges) =
16834 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16835 else {
16836 return;
16837 };
16838
16839 let mut best_length = usize::MAX;
16840 let mut best_inside = false;
16841 let mut best_in_bracket_range = false;
16842 let mut best_destination = None;
16843 for (open, close) in enclosing_bracket_ranges {
16844 let close = close.to_inclusive();
16845 let length = *close.end() - open.start;
16846 let inside = selection.start >= open.end && selection.end <= *close.start();
16847 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16848 || close.contains(&selection.head());
16849
16850 // If best is next to a bracket and current isn't, skip
16851 if !in_bracket_range && best_in_bracket_range {
16852 continue;
16853 }
16854
16855 // Prefer smaller lengths unless best is inside and current isn't
16856 if length > best_length && (best_inside || !inside) {
16857 continue;
16858 }
16859
16860 best_length = length;
16861 best_inside = inside;
16862 best_in_bracket_range = in_bracket_range;
16863 best_destination = Some(
16864 if close.contains(&selection.start) && close.contains(&selection.end) {
16865 if inside { open.end } else { open.start }
16866 } else if inside {
16867 *close.start()
16868 } else {
16869 *close.end()
16870 },
16871 );
16872 }
16873
16874 if let Some(destination) = best_destination {
16875 selection.collapse_to(destination, SelectionGoal::None);
16876 }
16877 })
16878 });
16879 }
16880
16881 pub fn undo_selection(
16882 &mut self,
16883 _: &UndoSelection,
16884 window: &mut Window,
16885 cx: &mut Context<Self>,
16886 ) {
16887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16888 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16889 self.selection_history.mode = SelectionHistoryMode::Undoing;
16890 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16891 this.end_selection(window, cx);
16892 this.change_selections(
16893 SelectionEffects::scroll(Autoscroll::newest()),
16894 window,
16895 cx,
16896 |s| s.select_anchors(entry.selections.to_vec()),
16897 );
16898 });
16899 self.selection_history.mode = SelectionHistoryMode::Normal;
16900
16901 self.select_next_state = entry.select_next_state;
16902 self.select_prev_state = entry.select_prev_state;
16903 self.add_selections_state = entry.add_selections_state;
16904 }
16905 }
16906
16907 pub fn redo_selection(
16908 &mut self,
16909 _: &RedoSelection,
16910 window: &mut Window,
16911 cx: &mut Context<Self>,
16912 ) {
16913 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16914 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16915 self.selection_history.mode = SelectionHistoryMode::Redoing;
16916 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16917 this.end_selection(window, cx);
16918 this.change_selections(
16919 SelectionEffects::scroll(Autoscroll::newest()),
16920 window,
16921 cx,
16922 |s| s.select_anchors(entry.selections.to_vec()),
16923 );
16924 });
16925 self.selection_history.mode = SelectionHistoryMode::Normal;
16926
16927 self.select_next_state = entry.select_next_state;
16928 self.select_prev_state = entry.select_prev_state;
16929 self.add_selections_state = entry.add_selections_state;
16930 }
16931 }
16932
16933 pub fn expand_excerpts(
16934 &mut self,
16935 action: &ExpandExcerpts,
16936 _: &mut Window,
16937 cx: &mut Context<Self>,
16938 ) {
16939 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16940 }
16941
16942 pub fn expand_excerpts_down(
16943 &mut self,
16944 action: &ExpandExcerptsDown,
16945 _: &mut Window,
16946 cx: &mut Context<Self>,
16947 ) {
16948 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16949 }
16950
16951 pub fn expand_excerpts_up(
16952 &mut self,
16953 action: &ExpandExcerptsUp,
16954 _: &mut Window,
16955 cx: &mut Context<Self>,
16956 ) {
16957 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16958 }
16959
16960 pub fn expand_excerpts_for_direction(
16961 &mut self,
16962 lines: u32,
16963 direction: ExpandExcerptDirection,
16964 cx: &mut Context<Self>,
16965 ) {
16966 let selections = self.selections.disjoint_anchors_arc();
16967
16968 let lines = if lines == 0 {
16969 EditorSettings::get_global(cx).expand_excerpt_lines
16970 } else {
16971 lines
16972 };
16973
16974 let snapshot = self.buffer.read(cx).snapshot(cx);
16975 let mut excerpt_ids = selections
16976 .iter()
16977 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16978 .collect::<Vec<_>>();
16979 excerpt_ids.sort();
16980 excerpt_ids.dedup();
16981
16982 if self.delegate_expand_excerpts {
16983 cx.emit(EditorEvent::ExpandExcerptsRequested {
16984 excerpt_ids,
16985 lines,
16986 direction,
16987 });
16988 return;
16989 }
16990
16991 self.buffer.update(cx, |buffer, cx| {
16992 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16993 })
16994 }
16995
16996 pub fn expand_excerpt(
16997 &mut self,
16998 excerpt: ExcerptId,
16999 direction: ExpandExcerptDirection,
17000 window: &mut Window,
17001 cx: &mut Context<Self>,
17002 ) {
17003 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17004
17005 if self.delegate_expand_excerpts {
17006 cx.emit(EditorEvent::ExpandExcerptsRequested {
17007 excerpt_ids: vec![excerpt],
17008 lines: lines_to_expand,
17009 direction,
17010 });
17011 return;
17012 }
17013
17014 let current_scroll_position = self.scroll_position(cx);
17015 let mut scroll = None;
17016
17017 if direction == ExpandExcerptDirection::Down {
17018 let multi_buffer = self.buffer.read(cx);
17019 let snapshot = multi_buffer.snapshot(cx);
17020 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17021 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17022 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17023 {
17024 let buffer_snapshot = buffer.read(cx).snapshot();
17025 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17026 let last_row = buffer_snapshot.max_point().row;
17027 let lines_below = last_row.saturating_sub(excerpt_end_row);
17028 if lines_below >= lines_to_expand {
17029 scroll = Some(
17030 current_scroll_position
17031 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17032 );
17033 }
17034 }
17035 }
17036 if direction == ExpandExcerptDirection::Up
17037 && self
17038 .buffer
17039 .read(cx)
17040 .snapshot(cx)
17041 .excerpt_before(excerpt)
17042 .is_none()
17043 {
17044 scroll = Some(current_scroll_position);
17045 }
17046
17047 self.buffer.update(cx, |buffer, cx| {
17048 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17049 });
17050
17051 if let Some(new_scroll_position) = scroll {
17052 self.set_scroll_position(new_scroll_position, window, cx);
17053 }
17054 }
17055
17056 pub fn go_to_singleton_buffer_point(
17057 &mut self,
17058 point: Point,
17059 window: &mut Window,
17060 cx: &mut Context<Self>,
17061 ) {
17062 self.go_to_singleton_buffer_range(point..point, window, cx);
17063 }
17064
17065 pub fn go_to_singleton_buffer_range(
17066 &mut self,
17067 range: Range<Point>,
17068 window: &mut Window,
17069 cx: &mut Context<Self>,
17070 ) {
17071 let multibuffer = self.buffer().read(cx);
17072 let Some(buffer) = multibuffer.as_singleton() else {
17073 return;
17074 };
17075 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17076 return;
17077 };
17078 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17079 return;
17080 };
17081 self.change_selections(
17082 SelectionEffects::default().nav_history(true),
17083 window,
17084 cx,
17085 |s| s.select_anchor_ranges([start..end]),
17086 );
17087 }
17088
17089 pub fn go_to_diagnostic(
17090 &mut self,
17091 action: &GoToDiagnostic,
17092 window: &mut Window,
17093 cx: &mut Context<Self>,
17094 ) {
17095 if !self.diagnostics_enabled() {
17096 return;
17097 }
17098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17099 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17100 }
17101
17102 pub fn go_to_prev_diagnostic(
17103 &mut self,
17104 action: &GoToPreviousDiagnostic,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 ) {
17108 if !self.diagnostics_enabled() {
17109 return;
17110 }
17111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17112 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17113 }
17114
17115 pub fn go_to_diagnostic_impl(
17116 &mut self,
17117 direction: Direction,
17118 severity: GoToDiagnosticSeverityFilter,
17119 window: &mut Window,
17120 cx: &mut Context<Self>,
17121 ) {
17122 let buffer = self.buffer.read(cx).snapshot(cx);
17123 let selection = self
17124 .selections
17125 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17126
17127 let mut active_group_id = None;
17128 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17129 && active_group.active_range.start.to_offset(&buffer) == selection.start
17130 {
17131 active_group_id = Some(active_group.group_id);
17132 }
17133
17134 fn filtered<'a>(
17135 severity: GoToDiagnosticSeverityFilter,
17136 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17137 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17138 diagnostics
17139 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17140 .filter(|entry| entry.range.start != entry.range.end)
17141 .filter(|entry| !entry.diagnostic.is_unnecessary)
17142 }
17143
17144 let before = filtered(
17145 severity,
17146 buffer
17147 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17148 .filter(|entry| entry.range.start <= selection.start),
17149 );
17150 let after = filtered(
17151 severity,
17152 buffer
17153 .diagnostics_in_range(selection.start..buffer.len())
17154 .filter(|entry| entry.range.start >= selection.start),
17155 );
17156
17157 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17158 if direction == Direction::Prev {
17159 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17160 {
17161 for diagnostic in prev_diagnostics.into_iter().rev() {
17162 if diagnostic.range.start != selection.start
17163 || active_group_id
17164 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17165 {
17166 found = Some(diagnostic);
17167 break 'outer;
17168 }
17169 }
17170 }
17171 } else {
17172 for diagnostic in after.chain(before) {
17173 if diagnostic.range.start != selection.start
17174 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17175 {
17176 found = Some(diagnostic);
17177 break;
17178 }
17179 }
17180 }
17181 let Some(next_diagnostic) = found else {
17182 return;
17183 };
17184
17185 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17186 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17187 return;
17188 };
17189 let snapshot = self.snapshot(window, cx);
17190 if snapshot.intersects_fold(next_diagnostic.range.start) {
17191 self.unfold_ranges(
17192 std::slice::from_ref(&next_diagnostic.range),
17193 true,
17194 false,
17195 cx,
17196 );
17197 }
17198 self.change_selections(Default::default(), window, cx, |s| {
17199 s.select_ranges(vec![
17200 next_diagnostic.range.start..next_diagnostic.range.start,
17201 ])
17202 });
17203 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17204 self.refresh_edit_prediction(false, true, window, cx);
17205 }
17206
17207 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17209 let snapshot = self.snapshot(window, cx);
17210 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17211 self.go_to_hunk_before_or_after_position(
17212 &snapshot,
17213 selection.head(),
17214 Direction::Next,
17215 window,
17216 cx,
17217 );
17218 }
17219
17220 pub fn go_to_hunk_before_or_after_position(
17221 &mut self,
17222 snapshot: &EditorSnapshot,
17223 position: Point,
17224 direction: Direction,
17225 window: &mut Window,
17226 cx: &mut Context<Editor>,
17227 ) {
17228 let row = if direction == Direction::Next {
17229 self.hunk_after_position(snapshot, position)
17230 .map(|hunk| hunk.row_range.start)
17231 } else {
17232 self.hunk_before_position(snapshot, position)
17233 };
17234
17235 if let Some(row) = row {
17236 let destination = Point::new(row.0, 0);
17237 let autoscroll = Autoscroll::center();
17238
17239 self.unfold_ranges(&[destination..destination], false, false, cx);
17240 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17241 s.select_ranges([destination..destination]);
17242 });
17243 }
17244 }
17245
17246 fn hunk_after_position(
17247 &mut self,
17248 snapshot: &EditorSnapshot,
17249 position: Point,
17250 ) -> Option<MultiBufferDiffHunk> {
17251 snapshot
17252 .buffer_snapshot()
17253 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17254 .find(|hunk| hunk.row_range.start.0 > position.row)
17255 .or_else(|| {
17256 snapshot
17257 .buffer_snapshot()
17258 .diff_hunks_in_range(Point::zero()..position)
17259 .find(|hunk| hunk.row_range.end.0 < position.row)
17260 })
17261 }
17262
17263 fn go_to_prev_hunk(
17264 &mut self,
17265 _: &GoToPreviousHunk,
17266 window: &mut Window,
17267 cx: &mut Context<Self>,
17268 ) {
17269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17270 let snapshot = self.snapshot(window, cx);
17271 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17272 self.go_to_hunk_before_or_after_position(
17273 &snapshot,
17274 selection.head(),
17275 Direction::Prev,
17276 window,
17277 cx,
17278 );
17279 }
17280
17281 fn hunk_before_position(
17282 &mut self,
17283 snapshot: &EditorSnapshot,
17284 position: Point,
17285 ) -> Option<MultiBufferRow> {
17286 snapshot
17287 .buffer_snapshot()
17288 .diff_hunk_before(position)
17289 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17290 }
17291
17292 fn go_to_next_change(
17293 &mut self,
17294 _: &GoToNextChange,
17295 window: &mut Window,
17296 cx: &mut Context<Self>,
17297 ) {
17298 if let Some(selections) = self
17299 .change_list
17300 .next_change(1, Direction::Next)
17301 .map(|s| s.to_vec())
17302 {
17303 self.change_selections(Default::default(), window, cx, |s| {
17304 let map = s.display_snapshot();
17305 s.select_display_ranges(selections.iter().map(|a| {
17306 let point = a.to_display_point(&map);
17307 point..point
17308 }))
17309 })
17310 }
17311 }
17312
17313 fn go_to_previous_change(
17314 &mut self,
17315 _: &GoToPreviousChange,
17316 window: &mut Window,
17317 cx: &mut Context<Self>,
17318 ) {
17319 if let Some(selections) = self
17320 .change_list
17321 .next_change(1, Direction::Prev)
17322 .map(|s| s.to_vec())
17323 {
17324 self.change_selections(Default::default(), window, cx, |s| {
17325 let map = s.display_snapshot();
17326 s.select_display_ranges(selections.iter().map(|a| {
17327 let point = a.to_display_point(&map);
17328 point..point
17329 }))
17330 })
17331 }
17332 }
17333
17334 pub fn go_to_next_document_highlight(
17335 &mut self,
17336 _: &GoToNextDocumentHighlight,
17337 window: &mut Window,
17338 cx: &mut Context<Self>,
17339 ) {
17340 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17341 }
17342
17343 pub fn go_to_prev_document_highlight(
17344 &mut self,
17345 _: &GoToPreviousDocumentHighlight,
17346 window: &mut Window,
17347 cx: &mut Context<Self>,
17348 ) {
17349 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17350 }
17351
17352 pub fn go_to_document_highlight_before_or_after_position(
17353 &mut self,
17354 direction: Direction,
17355 window: &mut Window,
17356 cx: &mut Context<Editor>,
17357 ) {
17358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17359 let snapshot = self.snapshot(window, cx);
17360 let buffer = &snapshot.buffer_snapshot();
17361 let position = self
17362 .selections
17363 .newest::<Point>(&snapshot.display_snapshot)
17364 .head();
17365 let anchor_position = buffer.anchor_after(position);
17366
17367 // Get all document highlights (both read and write)
17368 let mut all_highlights = Vec::new();
17369
17370 if let Some((_, read_highlights)) = self
17371 .background_highlights
17372 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17373 {
17374 all_highlights.extend(read_highlights.iter());
17375 }
17376
17377 if let Some((_, write_highlights)) = self
17378 .background_highlights
17379 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17380 {
17381 all_highlights.extend(write_highlights.iter());
17382 }
17383
17384 if all_highlights.is_empty() {
17385 return;
17386 }
17387
17388 // Sort highlights by position
17389 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17390
17391 let target_highlight = match direction {
17392 Direction::Next => {
17393 // Find the first highlight after the current position
17394 all_highlights
17395 .iter()
17396 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17397 }
17398 Direction::Prev => {
17399 // Find the last highlight before the current position
17400 all_highlights
17401 .iter()
17402 .rev()
17403 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17404 }
17405 };
17406
17407 if let Some(highlight) = target_highlight {
17408 let destination = highlight.start.to_point(buffer);
17409 let autoscroll = Autoscroll::center();
17410
17411 self.unfold_ranges(&[destination..destination], false, false, cx);
17412 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17413 s.select_ranges([destination..destination]);
17414 });
17415 }
17416 }
17417
17418 fn go_to_line<T: 'static>(
17419 &mut self,
17420 position: Anchor,
17421 highlight_color: Option<Hsla>,
17422 window: &mut Window,
17423 cx: &mut Context<Self>,
17424 ) {
17425 let snapshot = self.snapshot(window, cx).display_snapshot;
17426 let position = position.to_point(&snapshot.buffer_snapshot());
17427 let start = snapshot
17428 .buffer_snapshot()
17429 .clip_point(Point::new(position.row, 0), Bias::Left);
17430 let end = start + Point::new(1, 0);
17431 let start = snapshot.buffer_snapshot().anchor_before(start);
17432 let end = snapshot.buffer_snapshot().anchor_before(end);
17433
17434 self.highlight_rows::<T>(
17435 start..end,
17436 highlight_color
17437 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17438 Default::default(),
17439 cx,
17440 );
17441
17442 if self.buffer.read(cx).is_singleton() {
17443 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17444 }
17445 }
17446
17447 pub fn go_to_definition(
17448 &mut self,
17449 _: &GoToDefinition,
17450 window: &mut Window,
17451 cx: &mut Context<Self>,
17452 ) -> Task<Result<Navigated>> {
17453 let definition =
17454 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17455 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17456 cx.spawn_in(window, async move |editor, cx| {
17457 if definition.await? == Navigated::Yes {
17458 return Ok(Navigated::Yes);
17459 }
17460 match fallback_strategy {
17461 GoToDefinitionFallback::None => Ok(Navigated::No),
17462 GoToDefinitionFallback::FindAllReferences => {
17463 match editor.update_in(cx, |editor, window, cx| {
17464 editor.find_all_references(&FindAllReferences::default(), window, cx)
17465 })? {
17466 Some(references) => references.await,
17467 None => Ok(Navigated::No),
17468 }
17469 }
17470 }
17471 })
17472 }
17473
17474 pub fn go_to_declaration(
17475 &mut self,
17476 _: &GoToDeclaration,
17477 window: &mut Window,
17478 cx: &mut Context<Self>,
17479 ) -> Task<Result<Navigated>> {
17480 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17481 }
17482
17483 pub fn go_to_declaration_split(
17484 &mut self,
17485 _: &GoToDeclaration,
17486 window: &mut Window,
17487 cx: &mut Context<Self>,
17488 ) -> Task<Result<Navigated>> {
17489 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17490 }
17491
17492 pub fn go_to_implementation(
17493 &mut self,
17494 _: &GoToImplementation,
17495 window: &mut Window,
17496 cx: &mut Context<Self>,
17497 ) -> Task<Result<Navigated>> {
17498 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17499 }
17500
17501 pub fn go_to_implementation_split(
17502 &mut self,
17503 _: &GoToImplementationSplit,
17504 window: &mut Window,
17505 cx: &mut Context<Self>,
17506 ) -> Task<Result<Navigated>> {
17507 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17508 }
17509
17510 pub fn go_to_type_definition(
17511 &mut self,
17512 _: &GoToTypeDefinition,
17513 window: &mut Window,
17514 cx: &mut Context<Self>,
17515 ) -> Task<Result<Navigated>> {
17516 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17517 }
17518
17519 pub fn go_to_definition_split(
17520 &mut self,
17521 _: &GoToDefinitionSplit,
17522 window: &mut Window,
17523 cx: &mut Context<Self>,
17524 ) -> Task<Result<Navigated>> {
17525 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17526 }
17527
17528 pub fn go_to_type_definition_split(
17529 &mut self,
17530 _: &GoToTypeDefinitionSplit,
17531 window: &mut Window,
17532 cx: &mut Context<Self>,
17533 ) -> Task<Result<Navigated>> {
17534 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17535 }
17536
17537 fn go_to_definition_of_kind(
17538 &mut self,
17539 kind: GotoDefinitionKind,
17540 split: bool,
17541 window: &mut Window,
17542 cx: &mut Context<Self>,
17543 ) -> Task<Result<Navigated>> {
17544 let Some(provider) = self.semantics_provider.clone() else {
17545 return Task::ready(Ok(Navigated::No));
17546 };
17547 let head = self
17548 .selections
17549 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17550 .head();
17551 let buffer = self.buffer.read(cx);
17552 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17553 return Task::ready(Ok(Navigated::No));
17554 };
17555 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17556 return Task::ready(Ok(Navigated::No));
17557 };
17558
17559 cx.spawn_in(window, async move |editor, cx| {
17560 let Some(definitions) = definitions.await? else {
17561 return Ok(Navigated::No);
17562 };
17563 let navigated = editor
17564 .update_in(cx, |editor, window, cx| {
17565 editor.navigate_to_hover_links(
17566 Some(kind),
17567 definitions
17568 .into_iter()
17569 .filter(|location| {
17570 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17571 })
17572 .map(HoverLink::Text)
17573 .collect::<Vec<_>>(),
17574 split,
17575 window,
17576 cx,
17577 )
17578 })?
17579 .await?;
17580 anyhow::Ok(navigated)
17581 })
17582 }
17583
17584 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17585 let selection = self.selections.newest_anchor();
17586 let head = selection.head();
17587 let tail = selection.tail();
17588
17589 let Some((buffer, start_position)) =
17590 self.buffer.read(cx).text_anchor_for_position(head, cx)
17591 else {
17592 return;
17593 };
17594
17595 let end_position = if head != tail {
17596 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17597 return;
17598 };
17599 Some(pos)
17600 } else {
17601 None
17602 };
17603
17604 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17605 let url = if let Some(end_pos) = end_position {
17606 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17607 } else {
17608 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17609 };
17610
17611 if let Some(url) = url {
17612 cx.update(|window, cx| {
17613 if parse_zed_link(&url, cx).is_some() {
17614 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17615 } else {
17616 cx.open_url(&url);
17617 }
17618 })?;
17619 }
17620
17621 anyhow::Ok(())
17622 });
17623
17624 url_finder.detach();
17625 }
17626
17627 pub fn open_selected_filename(
17628 &mut self,
17629 _: &OpenSelectedFilename,
17630 window: &mut Window,
17631 cx: &mut Context<Self>,
17632 ) {
17633 let Some(workspace) = self.workspace() else {
17634 return;
17635 };
17636
17637 let position = self.selections.newest_anchor().head();
17638
17639 let Some((buffer, buffer_position)) =
17640 self.buffer.read(cx).text_anchor_for_position(position, cx)
17641 else {
17642 return;
17643 };
17644
17645 let project = self.project.clone();
17646
17647 cx.spawn_in(window, async move |_, cx| {
17648 let result = find_file(&buffer, project, buffer_position, cx).await;
17649
17650 if let Some((_, path)) = result {
17651 workspace
17652 .update_in(cx, |workspace, window, cx| {
17653 workspace.open_resolved_path(path, window, cx)
17654 })?
17655 .await?;
17656 }
17657 anyhow::Ok(())
17658 })
17659 .detach();
17660 }
17661
17662 pub(crate) fn navigate_to_hover_links(
17663 &mut self,
17664 kind: Option<GotoDefinitionKind>,
17665 definitions: Vec<HoverLink>,
17666 split: bool,
17667 window: &mut Window,
17668 cx: &mut Context<Editor>,
17669 ) -> Task<Result<Navigated>> {
17670 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17671 let mut first_url_or_file = None;
17672 let definitions: Vec<_> = definitions
17673 .into_iter()
17674 .filter_map(|def| match def {
17675 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17676 HoverLink::InlayHint(lsp_location, server_id) => {
17677 let computation =
17678 self.compute_target_location(lsp_location, server_id, window, cx);
17679 Some(cx.background_spawn(computation))
17680 }
17681 HoverLink::Url(url) => {
17682 first_url_or_file = Some(Either::Left(url));
17683 None
17684 }
17685 HoverLink::File(path) => {
17686 first_url_or_file = Some(Either::Right(path));
17687 None
17688 }
17689 })
17690 .collect();
17691
17692 let workspace = self.workspace();
17693
17694 cx.spawn_in(window, async move |editor, cx| {
17695 let locations: Vec<Location> = future::join_all(definitions)
17696 .await
17697 .into_iter()
17698 .filter_map(|location| location.transpose())
17699 .collect::<Result<_>>()
17700 .context("location tasks")?;
17701 let mut locations = cx.update(|_, cx| {
17702 locations
17703 .into_iter()
17704 .map(|location| {
17705 let buffer = location.buffer.read(cx);
17706 (location.buffer, location.range.to_point(buffer))
17707 })
17708 .into_group_map()
17709 })?;
17710 let mut num_locations = 0;
17711 for ranges in locations.values_mut() {
17712 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17713 ranges.dedup();
17714 num_locations += ranges.len();
17715 }
17716
17717 if num_locations > 1 {
17718 let tab_kind = match kind {
17719 Some(GotoDefinitionKind::Implementation) => "Implementations",
17720 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17721 Some(GotoDefinitionKind::Declaration) => "Declarations",
17722 Some(GotoDefinitionKind::Type) => "Types",
17723 };
17724 let title = editor
17725 .update_in(cx, |_, _, cx| {
17726 let target = locations
17727 .iter()
17728 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17729 .map(|(buffer, location)| {
17730 buffer
17731 .read(cx)
17732 .text_for_range(location.clone())
17733 .collect::<String>()
17734 })
17735 .filter(|text| !text.contains('\n'))
17736 .unique()
17737 .take(3)
17738 .join(", ");
17739 if target.is_empty() {
17740 tab_kind.to_owned()
17741 } else {
17742 format!("{tab_kind} for {target}")
17743 }
17744 })
17745 .context("buffer title")?;
17746
17747 let Some(workspace) = workspace else {
17748 return Ok(Navigated::No);
17749 };
17750
17751 let opened = workspace
17752 .update_in(cx, |workspace, window, cx| {
17753 let allow_preview = PreviewTabsSettings::get_global(cx)
17754 .enable_preview_multibuffer_from_code_navigation;
17755 Self::open_locations_in_multibuffer(
17756 workspace,
17757 locations,
17758 title,
17759 split,
17760 allow_preview,
17761 MultibufferSelectionMode::First,
17762 window,
17763 cx,
17764 )
17765 })
17766 .is_ok();
17767
17768 anyhow::Ok(Navigated::from_bool(opened))
17769 } else if num_locations == 0 {
17770 // If there is one url or file, open it directly
17771 match first_url_or_file {
17772 Some(Either::Left(url)) => {
17773 cx.update(|window, cx| {
17774 if parse_zed_link(&url, cx).is_some() {
17775 window
17776 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17777 } else {
17778 cx.open_url(&url);
17779 }
17780 })?;
17781 Ok(Navigated::Yes)
17782 }
17783 Some(Either::Right(path)) => {
17784 // TODO(andrew): respect preview tab settings
17785 // `enable_keep_preview_on_code_navigation` and
17786 // `enable_preview_file_from_code_navigation`
17787 let Some(workspace) = workspace else {
17788 return Ok(Navigated::No);
17789 };
17790 workspace
17791 .update_in(cx, |workspace, window, cx| {
17792 workspace.open_resolved_path(path, window, cx)
17793 })?
17794 .await?;
17795 Ok(Navigated::Yes)
17796 }
17797 None => Ok(Navigated::No),
17798 }
17799 } else {
17800 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17801 let target_range = target_ranges.first().unwrap().clone();
17802
17803 editor.update_in(cx, |editor, window, cx| {
17804 let range = target_range.to_point(target_buffer.read(cx));
17805 let range = editor.range_for_match(&range);
17806 let range = collapse_multiline_range(range);
17807
17808 if !split
17809 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17810 {
17811 editor.go_to_singleton_buffer_range(range, window, cx);
17812 } else {
17813 let Some(workspace) = workspace else {
17814 return Navigated::No;
17815 };
17816 let pane = workspace.read(cx).active_pane().clone();
17817 window.defer(cx, move |window, cx| {
17818 let target_editor: Entity<Self> =
17819 workspace.update(cx, |workspace, cx| {
17820 let pane = if split {
17821 workspace.adjacent_pane(window, cx)
17822 } else {
17823 workspace.active_pane().clone()
17824 };
17825
17826 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17827 let keep_old_preview = preview_tabs_settings
17828 .enable_keep_preview_on_code_navigation;
17829 let allow_new_preview = preview_tabs_settings
17830 .enable_preview_file_from_code_navigation;
17831
17832 workspace.open_project_item(
17833 pane,
17834 target_buffer.clone(),
17835 true,
17836 true,
17837 keep_old_preview,
17838 allow_new_preview,
17839 window,
17840 cx,
17841 )
17842 });
17843 target_editor.update(cx, |target_editor, cx| {
17844 // When selecting a definition in a different buffer, disable the nav history
17845 // to avoid creating a history entry at the previous cursor location.
17846 pane.update(cx, |pane, _| pane.disable_history());
17847 target_editor.go_to_singleton_buffer_range(range, window, cx);
17848 pane.update(cx, |pane, _| pane.enable_history());
17849 });
17850 });
17851 }
17852 Navigated::Yes
17853 })
17854 }
17855 })
17856 }
17857
17858 fn compute_target_location(
17859 &self,
17860 lsp_location: lsp::Location,
17861 server_id: LanguageServerId,
17862 window: &mut Window,
17863 cx: &mut Context<Self>,
17864 ) -> Task<anyhow::Result<Option<Location>>> {
17865 let Some(project) = self.project.clone() else {
17866 return Task::ready(Ok(None));
17867 };
17868
17869 cx.spawn_in(window, async move |editor, cx| {
17870 let location_task = editor.update(cx, |_, cx| {
17871 project.update(cx, |project, cx| {
17872 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17873 })
17874 })?;
17875 let location = Some({
17876 let target_buffer_handle = location_task.await.context("open local buffer")?;
17877 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17878 let target_start = target_buffer
17879 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17880 let target_end = target_buffer
17881 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17882 target_buffer.anchor_after(target_start)
17883 ..target_buffer.anchor_before(target_end)
17884 });
17885 Location {
17886 buffer: target_buffer_handle,
17887 range,
17888 }
17889 });
17890 Ok(location)
17891 })
17892 }
17893
17894 fn go_to_next_reference(
17895 &mut self,
17896 _: &GoToNextReference,
17897 window: &mut Window,
17898 cx: &mut Context<Self>,
17899 ) {
17900 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17901 if let Some(task) = task {
17902 task.detach();
17903 };
17904 }
17905
17906 fn go_to_prev_reference(
17907 &mut self,
17908 _: &GoToPreviousReference,
17909 window: &mut Window,
17910 cx: &mut Context<Self>,
17911 ) {
17912 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17913 if let Some(task) = task {
17914 task.detach();
17915 };
17916 }
17917
17918 pub fn go_to_reference_before_or_after_position(
17919 &mut self,
17920 direction: Direction,
17921 count: usize,
17922 window: &mut Window,
17923 cx: &mut Context<Self>,
17924 ) -> Option<Task<Result<()>>> {
17925 let selection = self.selections.newest_anchor();
17926 let head = selection.head();
17927
17928 let multi_buffer = self.buffer.read(cx);
17929
17930 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17931 let workspace = self.workspace()?;
17932 let project = workspace.read(cx).project().clone();
17933 let references =
17934 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17935 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17936 let Some(locations) = references.await? else {
17937 return Ok(());
17938 };
17939
17940 if locations.is_empty() {
17941 // totally normal - the cursor may be on something which is not
17942 // a symbol (e.g. a keyword)
17943 log::info!("no references found under cursor");
17944 return Ok(());
17945 }
17946
17947 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17948
17949 let (locations, current_location_index) =
17950 multi_buffer.update(cx, |multi_buffer, cx| {
17951 let mut locations = locations
17952 .into_iter()
17953 .filter_map(|loc| {
17954 let start = multi_buffer.buffer_anchor_to_anchor(
17955 &loc.buffer,
17956 loc.range.start,
17957 cx,
17958 )?;
17959 let end = multi_buffer.buffer_anchor_to_anchor(
17960 &loc.buffer,
17961 loc.range.end,
17962 cx,
17963 )?;
17964 Some(start..end)
17965 })
17966 .collect::<Vec<_>>();
17967
17968 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17969 // There is an O(n) implementation, but given this list will be
17970 // small (usually <100 items), the extra O(log(n)) factor isn't
17971 // worth the (surprisingly large amount of) extra complexity.
17972 locations
17973 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17974
17975 let head_offset = head.to_offset(&multi_buffer_snapshot);
17976
17977 let current_location_index = locations.iter().position(|loc| {
17978 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17979 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17980 });
17981
17982 (locations, current_location_index)
17983 });
17984
17985 let Some(current_location_index) = current_location_index else {
17986 // This indicates something has gone wrong, because we already
17987 // handle the "no references" case above
17988 log::error!(
17989 "failed to find current reference under cursor. Total references: {}",
17990 locations.len()
17991 );
17992 return Ok(());
17993 };
17994
17995 let destination_location_index = match direction {
17996 Direction::Next => (current_location_index + count) % locations.len(),
17997 Direction::Prev => {
17998 (current_location_index + locations.len() - count % locations.len())
17999 % locations.len()
18000 }
18001 };
18002
18003 // TODO(cameron): is this needed?
18004 // the thinking is to avoid "jumping to the current location" (avoid
18005 // polluting "jumplist" in vim terms)
18006 if current_location_index == destination_location_index {
18007 return Ok(());
18008 }
18009
18010 let Range { start, end } = locations[destination_location_index];
18011
18012 editor.update_in(cx, |editor, window, cx| {
18013 let effects = SelectionEffects::default();
18014
18015 editor.unfold_ranges(&[start..end], false, false, cx);
18016 editor.change_selections(effects, window, cx, |s| {
18017 s.select_ranges([start..start]);
18018 });
18019 })?;
18020
18021 Ok(())
18022 }))
18023 }
18024
18025 pub fn find_all_references(
18026 &mut self,
18027 action: &FindAllReferences,
18028 window: &mut Window,
18029 cx: &mut Context<Self>,
18030 ) -> Option<Task<Result<Navigated>>> {
18031 let always_open_multibuffer = action.always_open_multibuffer;
18032 let selection = self.selections.newest_anchor();
18033 let multi_buffer = self.buffer.read(cx);
18034 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18035 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18036 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18037 let head = selection_offset.head();
18038
18039 let head_anchor = multi_buffer_snapshot.anchor_at(
18040 head,
18041 if head < selection_offset.tail() {
18042 Bias::Right
18043 } else {
18044 Bias::Left
18045 },
18046 );
18047
18048 match self
18049 .find_all_references_task_sources
18050 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18051 {
18052 Ok(_) => {
18053 log::info!(
18054 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18055 );
18056 return None;
18057 }
18058 Err(i) => {
18059 self.find_all_references_task_sources.insert(i, head_anchor);
18060 }
18061 }
18062
18063 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18064 let workspace = self.workspace()?;
18065 let project = workspace.read(cx).project().clone();
18066 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18067 Some(cx.spawn_in(window, async move |editor, cx| {
18068 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18069 if let Ok(i) = editor
18070 .find_all_references_task_sources
18071 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18072 {
18073 editor.find_all_references_task_sources.remove(i);
18074 }
18075 });
18076
18077 let Some(locations) = references.await? else {
18078 return anyhow::Ok(Navigated::No);
18079 };
18080 let mut locations = cx.update(|_, cx| {
18081 locations
18082 .into_iter()
18083 .map(|location| {
18084 let buffer = location.buffer.read(cx);
18085 (location.buffer, location.range.to_point(buffer))
18086 })
18087 // if special-casing the single-match case, remove ranges
18088 // that intersect current selection
18089 .filter(|(location_buffer, location)| {
18090 if always_open_multibuffer || &buffer != location_buffer {
18091 return true;
18092 }
18093
18094 !location.contains_inclusive(&selection_point.range())
18095 })
18096 .into_group_map()
18097 })?;
18098 if locations.is_empty() {
18099 return anyhow::Ok(Navigated::No);
18100 }
18101 for ranges in locations.values_mut() {
18102 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18103 ranges.dedup();
18104 }
18105 let mut num_locations = 0;
18106 for ranges in locations.values_mut() {
18107 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18108 ranges.dedup();
18109 num_locations += ranges.len();
18110 }
18111
18112 if num_locations == 1 && !always_open_multibuffer {
18113 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18114 let target_range = target_ranges.first().unwrap().clone();
18115
18116 return editor.update_in(cx, |editor, window, cx| {
18117 let range = target_range.to_point(target_buffer.read(cx));
18118 let range = editor.range_for_match(&range);
18119 let range = range.start..range.start;
18120
18121 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18122 editor.go_to_singleton_buffer_range(range, window, cx);
18123 } else {
18124 let pane = workspace.read(cx).active_pane().clone();
18125 window.defer(cx, move |window, cx| {
18126 let target_editor: Entity<Self> =
18127 workspace.update(cx, |workspace, cx| {
18128 let pane = workspace.active_pane().clone();
18129
18130 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18131 let keep_old_preview = preview_tabs_settings
18132 .enable_keep_preview_on_code_navigation;
18133 let allow_new_preview = preview_tabs_settings
18134 .enable_preview_file_from_code_navigation;
18135
18136 workspace.open_project_item(
18137 pane,
18138 target_buffer.clone(),
18139 true,
18140 true,
18141 keep_old_preview,
18142 allow_new_preview,
18143 window,
18144 cx,
18145 )
18146 });
18147 target_editor.update(cx, |target_editor, cx| {
18148 // When selecting a definition in a different buffer, disable the nav history
18149 // to avoid creating a history entry at the previous cursor location.
18150 pane.update(cx, |pane, _| pane.disable_history());
18151 target_editor.go_to_singleton_buffer_range(range, window, cx);
18152 pane.update(cx, |pane, _| pane.enable_history());
18153 });
18154 });
18155 }
18156 Navigated::No
18157 });
18158 }
18159
18160 workspace.update_in(cx, |workspace, window, cx| {
18161 let target = locations
18162 .iter()
18163 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18164 .map(|(buffer, location)| {
18165 buffer
18166 .read(cx)
18167 .text_for_range(location.clone())
18168 .collect::<String>()
18169 })
18170 .filter(|text| !text.contains('\n'))
18171 .unique()
18172 .take(3)
18173 .join(", ");
18174 let title = if target.is_empty() {
18175 "References".to_owned()
18176 } else {
18177 format!("References to {target}")
18178 };
18179 let allow_preview = PreviewTabsSettings::get_global(cx)
18180 .enable_preview_multibuffer_from_code_navigation;
18181 Self::open_locations_in_multibuffer(
18182 workspace,
18183 locations,
18184 title,
18185 false,
18186 allow_preview,
18187 MultibufferSelectionMode::First,
18188 window,
18189 cx,
18190 );
18191 Navigated::Yes
18192 })
18193 }))
18194 }
18195
18196 /// Opens a multibuffer with the given project locations in it.
18197 pub fn open_locations_in_multibuffer(
18198 workspace: &mut Workspace,
18199 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18200 title: String,
18201 split: bool,
18202 allow_preview: bool,
18203 multibuffer_selection_mode: MultibufferSelectionMode,
18204 window: &mut Window,
18205 cx: &mut Context<Workspace>,
18206 ) {
18207 if locations.is_empty() {
18208 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18209 return;
18210 }
18211
18212 let capability = workspace.project().read(cx).capability();
18213 let mut ranges = <Vec<Range<Anchor>>>::new();
18214
18215 // a key to find existing multibuffer editors with the same set of locations
18216 // to prevent us from opening more and more multibuffer tabs for searches and the like
18217 let mut key = (title.clone(), vec![]);
18218 let excerpt_buffer = cx.new(|cx| {
18219 let key = &mut key.1;
18220 let mut multibuffer = MultiBuffer::new(capability);
18221 for (buffer, mut ranges_for_buffer) in locations {
18222 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18223 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18224 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18225 PathKey::for_buffer(&buffer, cx),
18226 buffer.clone(),
18227 ranges_for_buffer,
18228 multibuffer_context_lines(cx),
18229 cx,
18230 );
18231 ranges.extend(new_ranges)
18232 }
18233
18234 multibuffer.with_title(title)
18235 });
18236 let existing = workspace.active_pane().update(cx, |pane, cx| {
18237 pane.items()
18238 .filter_map(|item| item.downcast::<Editor>())
18239 .find(|editor| {
18240 editor
18241 .read(cx)
18242 .lookup_key
18243 .as_ref()
18244 .and_then(|it| {
18245 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18246 })
18247 .is_some_and(|it| *it == key)
18248 })
18249 });
18250 let was_existing = existing.is_some();
18251 let editor = existing.unwrap_or_else(|| {
18252 cx.new(|cx| {
18253 let mut editor = Editor::for_multibuffer(
18254 excerpt_buffer,
18255 Some(workspace.project().clone()),
18256 window,
18257 cx,
18258 );
18259 editor.lookup_key = Some(Box::new(key));
18260 editor
18261 })
18262 });
18263 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18264 MultibufferSelectionMode::First => {
18265 if let Some(first_range) = ranges.first() {
18266 editor.change_selections(
18267 SelectionEffects::no_scroll(),
18268 window,
18269 cx,
18270 |selections| {
18271 selections.clear_disjoint();
18272 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18273 },
18274 );
18275 }
18276 editor.highlight_background::<Self>(
18277 &ranges,
18278 |_, theme| theme.colors().editor_highlighted_line_background,
18279 cx,
18280 );
18281 }
18282 MultibufferSelectionMode::All => {
18283 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18284 selections.clear_disjoint();
18285 selections.select_anchor_ranges(ranges);
18286 });
18287 }
18288 });
18289
18290 let item = Box::new(editor);
18291
18292 let pane = if split {
18293 workspace.adjacent_pane(window, cx)
18294 } else {
18295 workspace.active_pane().clone()
18296 };
18297 let activate_pane = split;
18298
18299 let mut destination_index = None;
18300 pane.update(cx, |pane, cx| {
18301 if allow_preview && !was_existing {
18302 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18303 }
18304 if was_existing && !allow_preview {
18305 pane.unpreview_item_if_preview(item.item_id());
18306 }
18307 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18308 });
18309 }
18310
18311 pub fn rename(
18312 &mut self,
18313 _: &Rename,
18314 window: &mut Window,
18315 cx: &mut Context<Self>,
18316 ) -> Option<Task<Result<()>>> {
18317 use language::ToOffset as _;
18318
18319 let provider = self.semantics_provider.clone()?;
18320 let selection = self.selections.newest_anchor().clone();
18321 let (cursor_buffer, cursor_buffer_position) = self
18322 .buffer
18323 .read(cx)
18324 .text_anchor_for_position(selection.head(), cx)?;
18325 let (tail_buffer, cursor_buffer_position_end) = self
18326 .buffer
18327 .read(cx)
18328 .text_anchor_for_position(selection.tail(), cx)?;
18329 if tail_buffer != cursor_buffer {
18330 return None;
18331 }
18332
18333 let snapshot = cursor_buffer.read(cx).snapshot();
18334 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18335 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18336 let prepare_rename = provider
18337 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18338 .unwrap_or_else(|| Task::ready(Ok(None)));
18339 drop(snapshot);
18340
18341 Some(cx.spawn_in(window, async move |this, cx| {
18342 let rename_range = if let Some(range) = prepare_rename.await? {
18343 Some(range)
18344 } else {
18345 this.update(cx, |this, cx| {
18346 let buffer = this.buffer.read(cx).snapshot(cx);
18347 let mut buffer_highlights = this
18348 .document_highlights_for_position(selection.head(), &buffer)
18349 .filter(|highlight| {
18350 highlight.start.excerpt_id == selection.head().excerpt_id
18351 && highlight.end.excerpt_id == selection.head().excerpt_id
18352 });
18353 buffer_highlights
18354 .next()
18355 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18356 })?
18357 };
18358 if let Some(rename_range) = rename_range {
18359 this.update_in(cx, |this, window, cx| {
18360 let snapshot = cursor_buffer.read(cx).snapshot();
18361 let rename_buffer_range = rename_range.to_offset(&snapshot);
18362 let cursor_offset_in_rename_range =
18363 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18364 let cursor_offset_in_rename_range_end =
18365 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18366
18367 this.take_rename(false, window, cx);
18368 let buffer = this.buffer.read(cx).read(cx);
18369 let cursor_offset = selection.head().to_offset(&buffer);
18370 let rename_start =
18371 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18372 let rename_end = rename_start + rename_buffer_range.len();
18373 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18374 let mut old_highlight_id = None;
18375 let old_name: Arc<str> = buffer
18376 .chunks(rename_start..rename_end, true)
18377 .map(|chunk| {
18378 if old_highlight_id.is_none() {
18379 old_highlight_id = chunk.syntax_highlight_id;
18380 }
18381 chunk.text
18382 })
18383 .collect::<String>()
18384 .into();
18385
18386 drop(buffer);
18387
18388 // Position the selection in the rename editor so that it matches the current selection.
18389 this.show_local_selections = false;
18390 let rename_editor = cx.new(|cx| {
18391 let mut editor = Editor::single_line(window, cx);
18392 editor.buffer.update(cx, |buffer, cx| {
18393 buffer.edit(
18394 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18395 None,
18396 cx,
18397 )
18398 });
18399 let cursor_offset_in_rename_range =
18400 MultiBufferOffset(cursor_offset_in_rename_range);
18401 let cursor_offset_in_rename_range_end =
18402 MultiBufferOffset(cursor_offset_in_rename_range_end);
18403 let rename_selection_range = match cursor_offset_in_rename_range
18404 .cmp(&cursor_offset_in_rename_range_end)
18405 {
18406 Ordering::Equal => {
18407 editor.select_all(&SelectAll, window, cx);
18408 return editor;
18409 }
18410 Ordering::Less => {
18411 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18412 }
18413 Ordering::Greater => {
18414 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18415 }
18416 };
18417 if rename_selection_range.end.0 > old_name.len() {
18418 editor.select_all(&SelectAll, window, cx);
18419 } else {
18420 editor.change_selections(Default::default(), window, cx, |s| {
18421 s.select_ranges([rename_selection_range]);
18422 });
18423 }
18424 editor
18425 });
18426 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18427 if e == &EditorEvent::Focused {
18428 cx.emit(EditorEvent::FocusedIn)
18429 }
18430 })
18431 .detach();
18432
18433 let write_highlights =
18434 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18435 let read_highlights =
18436 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18437 let ranges = write_highlights
18438 .iter()
18439 .flat_map(|(_, ranges)| ranges.iter())
18440 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18441 .cloned()
18442 .collect();
18443
18444 this.highlight_text::<Rename>(
18445 ranges,
18446 HighlightStyle {
18447 fade_out: Some(0.6),
18448 ..Default::default()
18449 },
18450 cx,
18451 );
18452 let rename_focus_handle = rename_editor.focus_handle(cx);
18453 window.focus(&rename_focus_handle, cx);
18454 let block_id = this.insert_blocks(
18455 [BlockProperties {
18456 style: BlockStyle::Flex,
18457 placement: BlockPlacement::Below(range.start),
18458 height: Some(1),
18459 render: Arc::new({
18460 let rename_editor = rename_editor.clone();
18461 move |cx: &mut BlockContext| {
18462 let mut text_style = cx.editor_style.text.clone();
18463 if let Some(highlight_style) = old_highlight_id
18464 .and_then(|h| h.style(&cx.editor_style.syntax))
18465 {
18466 text_style = text_style.highlight(highlight_style);
18467 }
18468 div()
18469 .block_mouse_except_scroll()
18470 .pl(cx.anchor_x)
18471 .child(EditorElement::new(
18472 &rename_editor,
18473 EditorStyle {
18474 background: cx.theme().system().transparent,
18475 local_player: cx.editor_style.local_player,
18476 text: text_style,
18477 scrollbar_width: cx.editor_style.scrollbar_width,
18478 syntax: cx.editor_style.syntax.clone(),
18479 status: cx.editor_style.status.clone(),
18480 inlay_hints_style: HighlightStyle {
18481 font_weight: Some(FontWeight::BOLD),
18482 ..make_inlay_hints_style(cx.app)
18483 },
18484 edit_prediction_styles: make_suggestion_styles(
18485 cx.app,
18486 ),
18487 ..EditorStyle::default()
18488 },
18489 ))
18490 .into_any_element()
18491 }
18492 }),
18493 priority: 0,
18494 }],
18495 Some(Autoscroll::fit()),
18496 cx,
18497 )[0];
18498 this.pending_rename = Some(RenameState {
18499 range,
18500 old_name,
18501 editor: rename_editor,
18502 block_id,
18503 });
18504 })?;
18505 }
18506
18507 Ok(())
18508 }))
18509 }
18510
18511 pub fn confirm_rename(
18512 &mut self,
18513 _: &ConfirmRename,
18514 window: &mut Window,
18515 cx: &mut Context<Self>,
18516 ) -> Option<Task<Result<()>>> {
18517 let rename = self.take_rename(false, window, cx)?;
18518 let workspace = self.workspace()?.downgrade();
18519 let (buffer, start) = self
18520 .buffer
18521 .read(cx)
18522 .text_anchor_for_position(rename.range.start, cx)?;
18523 let (end_buffer, _) = self
18524 .buffer
18525 .read(cx)
18526 .text_anchor_for_position(rename.range.end, cx)?;
18527 if buffer != end_buffer {
18528 return None;
18529 }
18530
18531 let old_name = rename.old_name;
18532 let new_name = rename.editor.read(cx).text(cx);
18533
18534 let rename = self.semantics_provider.as_ref()?.perform_rename(
18535 &buffer,
18536 start,
18537 new_name.clone(),
18538 cx,
18539 )?;
18540
18541 Some(cx.spawn_in(window, async move |editor, cx| {
18542 let project_transaction = rename.await?;
18543 Self::open_project_transaction(
18544 &editor,
18545 workspace,
18546 project_transaction,
18547 format!("Rename: {} → {}", old_name, new_name),
18548 cx,
18549 )
18550 .await?;
18551
18552 editor.update(cx, |editor, cx| {
18553 editor.refresh_document_highlights(cx);
18554 })?;
18555 Ok(())
18556 }))
18557 }
18558
18559 fn take_rename(
18560 &mut self,
18561 moving_cursor: bool,
18562 window: &mut Window,
18563 cx: &mut Context<Self>,
18564 ) -> Option<RenameState> {
18565 let rename = self.pending_rename.take()?;
18566 if rename.editor.focus_handle(cx).is_focused(window) {
18567 window.focus(&self.focus_handle, cx);
18568 }
18569
18570 self.remove_blocks(
18571 [rename.block_id].into_iter().collect(),
18572 Some(Autoscroll::fit()),
18573 cx,
18574 );
18575 self.clear_highlights::<Rename>(cx);
18576 self.show_local_selections = true;
18577
18578 if moving_cursor {
18579 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18580 editor
18581 .selections
18582 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18583 .head()
18584 });
18585
18586 // Update the selection to match the position of the selection inside
18587 // the rename editor.
18588 let snapshot = self.buffer.read(cx).read(cx);
18589 let rename_range = rename.range.to_offset(&snapshot);
18590 let cursor_in_editor = snapshot
18591 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18592 .min(rename_range.end);
18593 drop(snapshot);
18594
18595 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18596 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18597 });
18598 } else {
18599 self.refresh_document_highlights(cx);
18600 }
18601
18602 Some(rename)
18603 }
18604
18605 pub fn pending_rename(&self) -> Option<&RenameState> {
18606 self.pending_rename.as_ref()
18607 }
18608
18609 fn format(
18610 &mut self,
18611 _: &Format,
18612 window: &mut Window,
18613 cx: &mut Context<Self>,
18614 ) -> Option<Task<Result<()>>> {
18615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18616
18617 let project = match &self.project {
18618 Some(project) => project.clone(),
18619 None => return None,
18620 };
18621
18622 Some(self.perform_format(
18623 project,
18624 FormatTrigger::Manual,
18625 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18626 window,
18627 cx,
18628 ))
18629 }
18630
18631 fn format_selections(
18632 &mut self,
18633 _: &FormatSelections,
18634 window: &mut Window,
18635 cx: &mut Context<Self>,
18636 ) -> Option<Task<Result<()>>> {
18637 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18638
18639 let project = match &self.project {
18640 Some(project) => project.clone(),
18641 None => return None,
18642 };
18643
18644 let ranges = self
18645 .selections
18646 .all_adjusted(&self.display_snapshot(cx))
18647 .into_iter()
18648 .map(|selection| selection.range())
18649 .collect_vec();
18650
18651 Some(self.perform_format(
18652 project,
18653 FormatTrigger::Manual,
18654 FormatTarget::Ranges(ranges),
18655 window,
18656 cx,
18657 ))
18658 }
18659
18660 fn perform_format(
18661 &mut self,
18662 project: Entity<Project>,
18663 trigger: FormatTrigger,
18664 target: FormatTarget,
18665 window: &mut Window,
18666 cx: &mut Context<Self>,
18667 ) -> Task<Result<()>> {
18668 let buffer = self.buffer.clone();
18669 let (buffers, target) = match target {
18670 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18671 FormatTarget::Ranges(selection_ranges) => {
18672 let multi_buffer = buffer.read(cx);
18673 let snapshot = multi_buffer.read(cx);
18674 let mut buffers = HashSet::default();
18675 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18676 BTreeMap::new();
18677 for selection_range in selection_ranges {
18678 for (buffer, buffer_range, _) in
18679 snapshot.range_to_buffer_ranges(selection_range)
18680 {
18681 let buffer_id = buffer.remote_id();
18682 let start = buffer.anchor_before(buffer_range.start);
18683 let end = buffer.anchor_after(buffer_range.end);
18684 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18685 buffer_id_to_ranges
18686 .entry(buffer_id)
18687 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18688 .or_insert_with(|| vec![start..end]);
18689 }
18690 }
18691 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18692 }
18693 };
18694
18695 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18696 let selections_prev = transaction_id_prev
18697 .and_then(|transaction_id_prev| {
18698 // default to selections as they were after the last edit, if we have them,
18699 // instead of how they are now.
18700 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18701 // will take you back to where you made the last edit, instead of staying where you scrolled
18702 self.selection_history
18703 .transaction(transaction_id_prev)
18704 .map(|t| t.0.clone())
18705 })
18706 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18707
18708 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18709 let format = project.update(cx, |project, cx| {
18710 project.format(buffers, target, true, trigger, cx)
18711 });
18712
18713 cx.spawn_in(window, async move |editor, cx| {
18714 let transaction = futures::select_biased! {
18715 transaction = format.log_err().fuse() => transaction,
18716 () = timeout => {
18717 log::warn!("timed out waiting for formatting");
18718 None
18719 }
18720 };
18721
18722 buffer.update(cx, |buffer, cx| {
18723 if let Some(transaction) = transaction
18724 && !buffer.is_singleton()
18725 {
18726 buffer.push_transaction(&transaction.0, cx);
18727 }
18728 cx.notify();
18729 });
18730
18731 if let Some(transaction_id_now) =
18732 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18733 {
18734 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18735 if has_new_transaction {
18736 editor
18737 .update(cx, |editor, _| {
18738 editor
18739 .selection_history
18740 .insert_transaction(transaction_id_now, selections_prev);
18741 })
18742 .ok();
18743 }
18744 }
18745
18746 Ok(())
18747 })
18748 }
18749
18750 fn organize_imports(
18751 &mut self,
18752 _: &OrganizeImports,
18753 window: &mut Window,
18754 cx: &mut Context<Self>,
18755 ) -> Option<Task<Result<()>>> {
18756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18757 let project = match &self.project {
18758 Some(project) => project.clone(),
18759 None => return None,
18760 };
18761 Some(self.perform_code_action_kind(
18762 project,
18763 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18764 window,
18765 cx,
18766 ))
18767 }
18768
18769 fn perform_code_action_kind(
18770 &mut self,
18771 project: Entity<Project>,
18772 kind: CodeActionKind,
18773 window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) -> Task<Result<()>> {
18776 let buffer = self.buffer.clone();
18777 let buffers = buffer.read(cx).all_buffers();
18778 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18779 let apply_action = project.update(cx, |project, cx| {
18780 project.apply_code_action_kind(buffers, kind, true, cx)
18781 });
18782 cx.spawn_in(window, async move |_, cx| {
18783 let transaction = futures::select_biased! {
18784 () = timeout => {
18785 log::warn!("timed out waiting for executing code action");
18786 None
18787 }
18788 transaction = apply_action.log_err().fuse() => transaction,
18789 };
18790 buffer.update(cx, |buffer, cx| {
18791 // check if we need this
18792 if let Some(transaction) = transaction
18793 && !buffer.is_singleton()
18794 {
18795 buffer.push_transaction(&transaction.0, cx);
18796 }
18797 cx.notify();
18798 });
18799 Ok(())
18800 })
18801 }
18802
18803 pub fn restart_language_server(
18804 &mut self,
18805 _: &RestartLanguageServer,
18806 _: &mut Window,
18807 cx: &mut Context<Self>,
18808 ) {
18809 if let Some(project) = self.project.clone() {
18810 self.buffer.update(cx, |multi_buffer, cx| {
18811 project.update(cx, |project, cx| {
18812 project.restart_language_servers_for_buffers(
18813 multi_buffer.all_buffers().into_iter().collect(),
18814 HashSet::default(),
18815 cx,
18816 );
18817 });
18818 })
18819 }
18820 }
18821
18822 pub fn stop_language_server(
18823 &mut self,
18824 _: &StopLanguageServer,
18825 _: &mut Window,
18826 cx: &mut Context<Self>,
18827 ) {
18828 if let Some(project) = self.project.clone() {
18829 self.buffer.update(cx, |multi_buffer, cx| {
18830 project.update(cx, |project, cx| {
18831 project.stop_language_servers_for_buffers(
18832 multi_buffer.all_buffers().into_iter().collect(),
18833 HashSet::default(),
18834 cx,
18835 );
18836 });
18837 });
18838 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18839 }
18840 }
18841
18842 fn cancel_language_server_work(
18843 workspace: &mut Workspace,
18844 _: &actions::CancelLanguageServerWork,
18845 _: &mut Window,
18846 cx: &mut Context<Workspace>,
18847 ) {
18848 let project = workspace.project();
18849 let buffers = workspace
18850 .active_item(cx)
18851 .and_then(|item| item.act_as::<Editor>(cx))
18852 .map_or(HashSet::default(), |editor| {
18853 editor.read(cx).buffer.read(cx).all_buffers()
18854 });
18855 project.update(cx, |project, cx| {
18856 project.cancel_language_server_work_for_buffers(buffers, cx);
18857 });
18858 }
18859
18860 fn show_character_palette(
18861 &mut self,
18862 _: &ShowCharacterPalette,
18863 window: &mut Window,
18864 _: &mut Context<Self>,
18865 ) {
18866 window.show_character_palette();
18867 }
18868
18869 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18870 if !self.diagnostics_enabled() {
18871 return;
18872 }
18873
18874 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18875 let buffer = self.buffer.read(cx).snapshot(cx);
18876 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18877 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18878 let is_valid = buffer
18879 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18880 .any(|entry| {
18881 entry.diagnostic.is_primary
18882 && !entry.range.is_empty()
18883 && entry.range.start == primary_range_start
18884 && entry.diagnostic.message == active_diagnostics.active_message
18885 });
18886
18887 if !is_valid {
18888 self.dismiss_diagnostics(cx);
18889 }
18890 }
18891 }
18892
18893 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18894 match &self.active_diagnostics {
18895 ActiveDiagnostic::Group(group) => Some(group),
18896 _ => None,
18897 }
18898 }
18899
18900 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18901 if !self.diagnostics_enabled() {
18902 return;
18903 }
18904 self.dismiss_diagnostics(cx);
18905 self.active_diagnostics = ActiveDiagnostic::All;
18906 }
18907
18908 fn activate_diagnostics(
18909 &mut self,
18910 buffer_id: BufferId,
18911 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18912 window: &mut Window,
18913 cx: &mut Context<Self>,
18914 ) {
18915 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18916 return;
18917 }
18918 self.dismiss_diagnostics(cx);
18919 let snapshot = self.snapshot(window, cx);
18920 let buffer = self.buffer.read(cx).snapshot(cx);
18921 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18922 return;
18923 };
18924
18925 let diagnostic_group = buffer
18926 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18927 .collect::<Vec<_>>();
18928
18929 let language_registry = self
18930 .project()
18931 .map(|project| project.read(cx).languages().clone());
18932
18933 let blocks = renderer.render_group(
18934 diagnostic_group,
18935 buffer_id,
18936 snapshot,
18937 cx.weak_entity(),
18938 language_registry,
18939 cx,
18940 );
18941
18942 let blocks = self.display_map.update(cx, |display_map, cx| {
18943 display_map.insert_blocks(blocks, cx).into_iter().collect()
18944 });
18945 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18946 active_range: buffer.anchor_before(diagnostic.range.start)
18947 ..buffer.anchor_after(diagnostic.range.end),
18948 active_message: diagnostic.diagnostic.message.clone(),
18949 group_id: diagnostic.diagnostic.group_id,
18950 blocks,
18951 });
18952 cx.notify();
18953 }
18954
18955 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18956 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18957 return;
18958 };
18959
18960 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18961 if let ActiveDiagnostic::Group(group) = prev {
18962 self.display_map.update(cx, |display_map, cx| {
18963 display_map.remove_blocks(group.blocks, cx);
18964 });
18965 cx.notify();
18966 }
18967 }
18968
18969 /// Disable inline diagnostics rendering for this editor.
18970 pub fn disable_inline_diagnostics(&mut self) {
18971 self.inline_diagnostics_enabled = false;
18972 self.inline_diagnostics_update = Task::ready(());
18973 self.inline_diagnostics.clear();
18974 }
18975
18976 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18977 self.diagnostics_enabled = false;
18978 self.dismiss_diagnostics(cx);
18979 self.inline_diagnostics_update = Task::ready(());
18980 self.inline_diagnostics.clear();
18981 }
18982
18983 pub fn disable_word_completions(&mut self) {
18984 self.word_completions_enabled = false;
18985 }
18986
18987 pub fn diagnostics_enabled(&self) -> bool {
18988 self.diagnostics_enabled && self.mode.is_full()
18989 }
18990
18991 pub fn inline_diagnostics_enabled(&self) -> bool {
18992 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18993 }
18994
18995 pub fn show_inline_diagnostics(&self) -> bool {
18996 self.show_inline_diagnostics
18997 }
18998
18999 pub fn toggle_inline_diagnostics(
19000 &mut self,
19001 _: &ToggleInlineDiagnostics,
19002 window: &mut Window,
19003 cx: &mut Context<Editor>,
19004 ) {
19005 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19006 self.refresh_inline_diagnostics(false, window, cx);
19007 }
19008
19009 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19010 self.diagnostics_max_severity = severity;
19011 self.display_map.update(cx, |display_map, _| {
19012 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19013 });
19014 }
19015
19016 pub fn toggle_diagnostics(
19017 &mut self,
19018 _: &ToggleDiagnostics,
19019 window: &mut Window,
19020 cx: &mut Context<Editor>,
19021 ) {
19022 if !self.diagnostics_enabled() {
19023 return;
19024 }
19025
19026 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19027 EditorSettings::get_global(cx)
19028 .diagnostics_max_severity
19029 .filter(|severity| severity != &DiagnosticSeverity::Off)
19030 .unwrap_or(DiagnosticSeverity::Hint)
19031 } else {
19032 DiagnosticSeverity::Off
19033 };
19034 self.set_max_diagnostics_severity(new_severity, cx);
19035 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19036 self.active_diagnostics = ActiveDiagnostic::None;
19037 self.inline_diagnostics_update = Task::ready(());
19038 self.inline_diagnostics.clear();
19039 } else {
19040 self.refresh_inline_diagnostics(false, window, cx);
19041 }
19042
19043 cx.notify();
19044 }
19045
19046 pub fn toggle_minimap(
19047 &mut self,
19048 _: &ToggleMinimap,
19049 window: &mut Window,
19050 cx: &mut Context<Editor>,
19051 ) {
19052 if self.supports_minimap(cx) {
19053 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19054 }
19055 }
19056
19057 fn refresh_inline_diagnostics(
19058 &mut self,
19059 debounce: bool,
19060 window: &mut Window,
19061 cx: &mut Context<Self>,
19062 ) {
19063 let max_severity = ProjectSettings::get_global(cx)
19064 .diagnostics
19065 .inline
19066 .max_severity
19067 .unwrap_or(self.diagnostics_max_severity);
19068
19069 if !self.inline_diagnostics_enabled()
19070 || !self.diagnostics_enabled()
19071 || !self.show_inline_diagnostics
19072 || max_severity == DiagnosticSeverity::Off
19073 {
19074 self.inline_diagnostics_update = Task::ready(());
19075 self.inline_diagnostics.clear();
19076 return;
19077 }
19078
19079 let debounce_ms = ProjectSettings::get_global(cx)
19080 .diagnostics
19081 .inline
19082 .update_debounce_ms;
19083 let debounce = if debounce && debounce_ms > 0 {
19084 Some(Duration::from_millis(debounce_ms))
19085 } else {
19086 None
19087 };
19088 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19089 if let Some(debounce) = debounce {
19090 cx.background_executor().timer(debounce).await;
19091 }
19092 let Some(snapshot) = editor.upgrade().map(|editor| {
19093 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19094 }) else {
19095 return;
19096 };
19097
19098 let new_inline_diagnostics = cx
19099 .background_spawn(async move {
19100 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19101 for diagnostic_entry in
19102 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19103 {
19104 let message = diagnostic_entry
19105 .diagnostic
19106 .message
19107 .split_once('\n')
19108 .map(|(line, _)| line)
19109 .map(SharedString::new)
19110 .unwrap_or_else(|| {
19111 SharedString::new(&*diagnostic_entry.diagnostic.message)
19112 });
19113 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19114 let (Ok(i) | Err(i)) = inline_diagnostics
19115 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19116 inline_diagnostics.insert(
19117 i,
19118 (
19119 start_anchor,
19120 InlineDiagnostic {
19121 message,
19122 group_id: diagnostic_entry.diagnostic.group_id,
19123 start: diagnostic_entry.range.start.to_point(&snapshot),
19124 is_primary: diagnostic_entry.diagnostic.is_primary,
19125 severity: diagnostic_entry.diagnostic.severity,
19126 },
19127 ),
19128 );
19129 }
19130 inline_diagnostics
19131 })
19132 .await;
19133
19134 editor
19135 .update(cx, |editor, cx| {
19136 editor.inline_diagnostics = new_inline_diagnostics;
19137 cx.notify();
19138 })
19139 .ok();
19140 });
19141 }
19142
19143 fn pull_diagnostics(
19144 &mut self,
19145 buffer_id: Option<BufferId>,
19146 window: &Window,
19147 cx: &mut Context<Self>,
19148 ) -> Option<()> {
19149 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
19150 return None;
19151 }
19152 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19153 .diagnostics
19154 .lsp_pull_diagnostics;
19155 if !pull_diagnostics_settings.enabled {
19156 return None;
19157 }
19158 let project = self.project()?.downgrade();
19159
19160 let mut edited_buffer_ids = HashSet::default();
19161 let mut edited_worktree_ids = HashSet::default();
19162 let edited_buffers = match buffer_id {
19163 Some(buffer_id) => {
19164 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19165 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
19166 edited_buffer_ids.insert(buffer.read(cx).remote_id());
19167 edited_worktree_ids.insert(worktree_id);
19168 vec![buffer]
19169 }
19170 None => self
19171 .buffer()
19172 .read(cx)
19173 .all_buffers()
19174 .into_iter()
19175 .filter(|buffer| {
19176 let buffer = buffer.read(cx);
19177 match buffer.file().map(|f| f.worktree_id(cx)) {
19178 Some(worktree_id) => {
19179 edited_buffer_ids.insert(buffer.remote_id());
19180 edited_worktree_ids.insert(worktree_id);
19181 true
19182 }
19183 None => false,
19184 }
19185 })
19186 .collect::<Vec<_>>(),
19187 };
19188
19189 if edited_buffers.is_empty() {
19190 self.pull_diagnostics_task = Task::ready(());
19191 self.pull_diagnostics_background_task = Task::ready(());
19192 return None;
19193 }
19194
19195 let mut already_used_buffers = HashSet::default();
19196 let related_open_buffers = self
19197 .workspace
19198 .as_ref()
19199 .and_then(|(workspace, _)| workspace.upgrade())
19200 .into_iter()
19201 .flat_map(|workspace| workspace.read(cx).panes())
19202 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
19203 .filter(|editor| editor != &cx.entity())
19204 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
19205 .filter(|buffer| {
19206 let buffer = buffer.read(cx);
19207 let buffer_id = buffer.remote_id();
19208 if already_used_buffers.insert(buffer_id) {
19209 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
19210 return !edited_buffer_ids.contains(&buffer_id)
19211 && edited_worktree_ids.contains(&worktree_id);
19212 }
19213 }
19214 false
19215 })
19216 .collect::<Vec<_>>();
19217
19218 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19219 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
19220 if buffers.is_empty() {
19221 return Task::ready(());
19222 }
19223 let project_weak = project.clone();
19224 cx.spawn_in(window, async move |_, cx| {
19225 cx.background_executor().timer(delay).await;
19226
19227 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
19228 buffers
19229 .into_iter()
19230 .filter_map(|buffer| {
19231 project_weak
19232 .update(cx, |project, cx| {
19233 project.lsp_store().update(cx, |lsp_store, cx| {
19234 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19235 })
19236 })
19237 .ok()
19238 })
19239 .collect::<FuturesUnordered<_>>()
19240 }) else {
19241 return;
19242 };
19243
19244 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
19245 if let Err(e) = pull_task {
19246 log::error!("Failed to update project diagnostics: {e:#}");
19247 }
19248 }
19249 })
19250 };
19251
19252 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
19253 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
19254
19255 Some(())
19256 }
19257
19258 pub fn set_selections_from_remote(
19259 &mut self,
19260 selections: Vec<Selection<Anchor>>,
19261 pending_selection: Option<Selection<Anchor>>,
19262 window: &mut Window,
19263 cx: &mut Context<Self>,
19264 ) {
19265 let old_cursor_position = self.selections.newest_anchor().head();
19266 self.selections
19267 .change_with(&self.display_snapshot(cx), |s| {
19268 s.select_anchors(selections);
19269 if let Some(pending_selection) = pending_selection {
19270 s.set_pending(pending_selection, SelectMode::Character);
19271 } else {
19272 s.clear_pending();
19273 }
19274 });
19275 self.selections_did_change(
19276 false,
19277 &old_cursor_position,
19278 SelectionEffects::default(),
19279 window,
19280 cx,
19281 );
19282 }
19283
19284 pub fn transact(
19285 &mut self,
19286 window: &mut Window,
19287 cx: &mut Context<Self>,
19288 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19289 ) -> Option<TransactionId> {
19290 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19291 this.start_transaction_at(Instant::now(), window, cx);
19292 update(this, window, cx);
19293 this.end_transaction_at(Instant::now(), cx)
19294 })
19295 }
19296
19297 pub fn start_transaction_at(
19298 &mut self,
19299 now: Instant,
19300 window: &mut Window,
19301 cx: &mut Context<Self>,
19302 ) -> Option<TransactionId> {
19303 self.end_selection(window, cx);
19304 if let Some(tx_id) = self
19305 .buffer
19306 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19307 {
19308 self.selection_history
19309 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19310 cx.emit(EditorEvent::TransactionBegun {
19311 transaction_id: tx_id,
19312 });
19313 Some(tx_id)
19314 } else {
19315 None
19316 }
19317 }
19318
19319 pub fn end_transaction_at(
19320 &mut self,
19321 now: Instant,
19322 cx: &mut Context<Self>,
19323 ) -> Option<TransactionId> {
19324 if let Some(transaction_id) = self
19325 .buffer
19326 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19327 {
19328 if let Some((_, end_selections)) =
19329 self.selection_history.transaction_mut(transaction_id)
19330 {
19331 *end_selections = Some(self.selections.disjoint_anchors_arc());
19332 } else {
19333 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19334 }
19335
19336 cx.emit(EditorEvent::Edited { transaction_id });
19337 Some(transaction_id)
19338 } else {
19339 None
19340 }
19341 }
19342
19343 pub fn modify_transaction_selection_history(
19344 &mut self,
19345 transaction_id: TransactionId,
19346 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19347 ) -> bool {
19348 self.selection_history
19349 .transaction_mut(transaction_id)
19350 .map(modify)
19351 .is_some()
19352 }
19353
19354 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19355 if self.selection_mark_mode {
19356 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19357 s.move_with(|_, sel| {
19358 sel.collapse_to(sel.head(), SelectionGoal::None);
19359 });
19360 })
19361 }
19362 self.selection_mark_mode = true;
19363 cx.notify();
19364 }
19365
19366 pub fn swap_selection_ends(
19367 &mut self,
19368 _: &actions::SwapSelectionEnds,
19369 window: &mut Window,
19370 cx: &mut Context<Self>,
19371 ) {
19372 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19373 s.move_with(|_, sel| {
19374 if sel.start != sel.end {
19375 sel.reversed = !sel.reversed
19376 }
19377 });
19378 });
19379 self.request_autoscroll(Autoscroll::newest(), cx);
19380 cx.notify();
19381 }
19382
19383 pub fn toggle_focus(
19384 workspace: &mut Workspace,
19385 _: &actions::ToggleFocus,
19386 window: &mut Window,
19387 cx: &mut Context<Workspace>,
19388 ) {
19389 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19390 return;
19391 };
19392 workspace.activate_item(&item, true, true, window, cx);
19393 }
19394
19395 pub fn toggle_fold(
19396 &mut self,
19397 _: &actions::ToggleFold,
19398 window: &mut Window,
19399 cx: &mut Context<Self>,
19400 ) {
19401 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19402 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19403 let selection = self.selections.newest::<Point>(&display_map);
19404
19405 let range = if selection.is_empty() {
19406 let point = selection.head().to_display_point(&display_map);
19407 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19408 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19409 .to_point(&display_map);
19410 start..end
19411 } else {
19412 selection.range()
19413 };
19414 if display_map.folds_in_range(range).next().is_some() {
19415 self.unfold_lines(&Default::default(), window, cx)
19416 } else {
19417 self.fold(&Default::default(), window, cx)
19418 }
19419 } else {
19420 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19421 let buffer_ids: HashSet<_> = self
19422 .selections
19423 .disjoint_anchor_ranges()
19424 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19425 .collect();
19426
19427 let should_unfold = buffer_ids
19428 .iter()
19429 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19430
19431 for buffer_id in buffer_ids {
19432 if should_unfold {
19433 self.unfold_buffer(buffer_id, cx);
19434 } else {
19435 self.fold_buffer(buffer_id, cx);
19436 }
19437 }
19438 }
19439 }
19440
19441 pub fn toggle_fold_recursive(
19442 &mut self,
19443 _: &actions::ToggleFoldRecursive,
19444 window: &mut Window,
19445 cx: &mut Context<Self>,
19446 ) {
19447 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19448
19449 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19450 let range = if selection.is_empty() {
19451 let point = selection.head().to_display_point(&display_map);
19452 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19453 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19454 .to_point(&display_map);
19455 start..end
19456 } else {
19457 selection.range()
19458 };
19459 if display_map.folds_in_range(range).next().is_some() {
19460 self.unfold_recursive(&Default::default(), window, cx)
19461 } else {
19462 self.fold_recursive(&Default::default(), window, cx)
19463 }
19464 }
19465
19466 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19467 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19468 let mut to_fold = Vec::new();
19469 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19470 let selections = self.selections.all_adjusted(&display_map);
19471
19472 for selection in selections {
19473 let range = selection.range().sorted();
19474 let buffer_start_row = range.start.row;
19475
19476 if range.start.row != range.end.row {
19477 let mut found = false;
19478 let mut row = range.start.row;
19479 while row <= range.end.row {
19480 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19481 {
19482 found = true;
19483 row = crease.range().end.row + 1;
19484 to_fold.push(crease);
19485 } else {
19486 row += 1
19487 }
19488 }
19489 if found {
19490 continue;
19491 }
19492 }
19493
19494 for row in (0..=range.start.row).rev() {
19495 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19496 && crease.range().end.row >= buffer_start_row
19497 {
19498 to_fold.push(crease);
19499 if row <= range.start.row {
19500 break;
19501 }
19502 }
19503 }
19504 }
19505
19506 self.fold_creases(to_fold, true, window, cx);
19507 } else {
19508 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19509 let buffer_ids = self
19510 .selections
19511 .disjoint_anchor_ranges()
19512 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19513 .collect::<HashSet<_>>();
19514 for buffer_id in buffer_ids {
19515 self.fold_buffer(buffer_id, cx);
19516 }
19517 }
19518 }
19519
19520 pub fn toggle_fold_all(
19521 &mut self,
19522 _: &actions::ToggleFoldAll,
19523 window: &mut Window,
19524 cx: &mut Context<Self>,
19525 ) {
19526 let has_folds = if self.buffer.read(cx).is_singleton() {
19527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19528 let has_folds = display_map
19529 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19530 .next()
19531 .is_some();
19532 has_folds
19533 } else {
19534 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19535 let has_folds = buffer_ids
19536 .iter()
19537 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19538 has_folds
19539 };
19540
19541 if has_folds {
19542 self.unfold_all(&actions::UnfoldAll, window, cx);
19543 } else {
19544 self.fold_all(&actions::FoldAll, window, cx);
19545 }
19546 }
19547
19548 fn fold_at_level(
19549 &mut self,
19550 fold_at: &FoldAtLevel,
19551 window: &mut Window,
19552 cx: &mut Context<Self>,
19553 ) {
19554 if !self.buffer.read(cx).is_singleton() {
19555 return;
19556 }
19557
19558 let fold_at_level = fold_at.0;
19559 let snapshot = self.buffer.read(cx).snapshot(cx);
19560 let mut to_fold = Vec::new();
19561 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19562
19563 let row_ranges_to_keep: Vec<Range<u32>> = self
19564 .selections
19565 .all::<Point>(&self.display_snapshot(cx))
19566 .into_iter()
19567 .map(|sel| sel.start.row..sel.end.row)
19568 .collect();
19569
19570 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19571 while start_row < end_row {
19572 match self
19573 .snapshot(window, cx)
19574 .crease_for_buffer_row(MultiBufferRow(start_row))
19575 {
19576 Some(crease) => {
19577 let nested_start_row = crease.range().start.row + 1;
19578 let nested_end_row = crease.range().end.row;
19579
19580 if current_level < fold_at_level {
19581 stack.push((nested_start_row, nested_end_row, current_level + 1));
19582 } else if current_level == fold_at_level {
19583 // Fold iff there is no selection completely contained within the fold region
19584 if !row_ranges_to_keep.iter().any(|selection| {
19585 selection.end >= nested_start_row
19586 && selection.start <= nested_end_row
19587 }) {
19588 to_fold.push(crease);
19589 }
19590 }
19591
19592 start_row = nested_end_row + 1;
19593 }
19594 None => start_row += 1,
19595 }
19596 }
19597 }
19598
19599 self.fold_creases(to_fold, true, window, cx);
19600 }
19601
19602 pub fn fold_at_level_1(
19603 &mut self,
19604 _: &actions::FoldAtLevel1,
19605 window: &mut Window,
19606 cx: &mut Context<Self>,
19607 ) {
19608 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19609 }
19610
19611 pub fn fold_at_level_2(
19612 &mut self,
19613 _: &actions::FoldAtLevel2,
19614 window: &mut Window,
19615 cx: &mut Context<Self>,
19616 ) {
19617 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19618 }
19619
19620 pub fn fold_at_level_3(
19621 &mut self,
19622 _: &actions::FoldAtLevel3,
19623 window: &mut Window,
19624 cx: &mut Context<Self>,
19625 ) {
19626 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19627 }
19628
19629 pub fn fold_at_level_4(
19630 &mut self,
19631 _: &actions::FoldAtLevel4,
19632 window: &mut Window,
19633 cx: &mut Context<Self>,
19634 ) {
19635 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19636 }
19637
19638 pub fn fold_at_level_5(
19639 &mut self,
19640 _: &actions::FoldAtLevel5,
19641 window: &mut Window,
19642 cx: &mut Context<Self>,
19643 ) {
19644 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19645 }
19646
19647 pub fn fold_at_level_6(
19648 &mut self,
19649 _: &actions::FoldAtLevel6,
19650 window: &mut Window,
19651 cx: &mut Context<Self>,
19652 ) {
19653 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19654 }
19655
19656 pub fn fold_at_level_7(
19657 &mut self,
19658 _: &actions::FoldAtLevel7,
19659 window: &mut Window,
19660 cx: &mut Context<Self>,
19661 ) {
19662 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19663 }
19664
19665 pub fn fold_at_level_8(
19666 &mut self,
19667 _: &actions::FoldAtLevel8,
19668 window: &mut Window,
19669 cx: &mut Context<Self>,
19670 ) {
19671 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19672 }
19673
19674 pub fn fold_at_level_9(
19675 &mut self,
19676 _: &actions::FoldAtLevel9,
19677 window: &mut Window,
19678 cx: &mut Context<Self>,
19679 ) {
19680 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19681 }
19682
19683 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19684 if self.buffer.read(cx).is_singleton() {
19685 let mut fold_ranges = Vec::new();
19686 let snapshot = self.buffer.read(cx).snapshot(cx);
19687
19688 for row in 0..snapshot.max_row().0 {
19689 if let Some(foldable_range) = self
19690 .snapshot(window, cx)
19691 .crease_for_buffer_row(MultiBufferRow(row))
19692 {
19693 fold_ranges.push(foldable_range);
19694 }
19695 }
19696
19697 self.fold_creases(fold_ranges, true, window, cx);
19698 } else {
19699 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19700 editor
19701 .update_in(cx, |editor, _, cx| {
19702 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19703 editor.fold_buffer(buffer_id, cx);
19704 }
19705 })
19706 .ok();
19707 });
19708 }
19709 cx.emit(SearchEvent::ResultsCollapsedChanged(
19710 CollapseDirection::Collapsed,
19711 ));
19712 }
19713
19714 pub fn fold_function_bodies(
19715 &mut self,
19716 _: &actions::FoldFunctionBodies,
19717 window: &mut Window,
19718 cx: &mut Context<Self>,
19719 ) {
19720 let snapshot = self.buffer.read(cx).snapshot(cx);
19721
19722 let ranges = snapshot
19723 .text_object_ranges(
19724 MultiBufferOffset(0)..snapshot.len(),
19725 TreeSitterOptions::default(),
19726 )
19727 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19728 .collect::<Vec<_>>();
19729
19730 let creases = ranges
19731 .into_iter()
19732 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19733 .collect();
19734
19735 self.fold_creases(creases, true, window, cx);
19736 }
19737
19738 pub fn fold_recursive(
19739 &mut self,
19740 _: &actions::FoldRecursive,
19741 window: &mut Window,
19742 cx: &mut Context<Self>,
19743 ) {
19744 let mut to_fold = Vec::new();
19745 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19746 let selections = self.selections.all_adjusted(&display_map);
19747
19748 for selection in selections {
19749 let range = selection.range().sorted();
19750 let buffer_start_row = range.start.row;
19751
19752 if range.start.row != range.end.row {
19753 let mut found = false;
19754 for row in range.start.row..=range.end.row {
19755 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19756 found = true;
19757 to_fold.push(crease);
19758 }
19759 }
19760 if found {
19761 continue;
19762 }
19763 }
19764
19765 for row in (0..=range.start.row).rev() {
19766 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19767 if crease.range().end.row >= buffer_start_row {
19768 to_fold.push(crease);
19769 } else {
19770 break;
19771 }
19772 }
19773 }
19774 }
19775
19776 self.fold_creases(to_fold, true, window, cx);
19777 }
19778
19779 pub fn fold_at(
19780 &mut self,
19781 buffer_row: MultiBufferRow,
19782 window: &mut Window,
19783 cx: &mut Context<Self>,
19784 ) {
19785 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19786
19787 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19788 let autoscroll = self
19789 .selections
19790 .all::<Point>(&display_map)
19791 .iter()
19792 .any(|selection| crease.range().overlaps(&selection.range()));
19793
19794 self.fold_creases(vec![crease], autoscroll, window, cx);
19795 }
19796 }
19797
19798 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19799 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19800 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19801 let buffer = display_map.buffer_snapshot();
19802 let selections = self.selections.all::<Point>(&display_map);
19803 let ranges = selections
19804 .iter()
19805 .map(|s| {
19806 let range = s.display_range(&display_map).sorted();
19807 let mut start = range.start.to_point(&display_map);
19808 let mut end = range.end.to_point(&display_map);
19809 start.column = 0;
19810 end.column = buffer.line_len(MultiBufferRow(end.row));
19811 start..end
19812 })
19813 .collect::<Vec<_>>();
19814
19815 self.unfold_ranges(&ranges, true, true, cx);
19816 } else {
19817 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19818 let buffer_ids = self
19819 .selections
19820 .disjoint_anchor_ranges()
19821 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19822 .collect::<HashSet<_>>();
19823 for buffer_id in buffer_ids {
19824 self.unfold_buffer(buffer_id, cx);
19825 }
19826 }
19827 }
19828
19829 pub fn unfold_recursive(
19830 &mut self,
19831 _: &UnfoldRecursive,
19832 _window: &mut Window,
19833 cx: &mut Context<Self>,
19834 ) {
19835 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19836 let selections = self.selections.all::<Point>(&display_map);
19837 let ranges = selections
19838 .iter()
19839 .map(|s| {
19840 let mut range = s.display_range(&display_map).sorted();
19841 *range.start.column_mut() = 0;
19842 *range.end.column_mut() = display_map.line_len(range.end.row());
19843 let start = range.start.to_point(&display_map);
19844 let end = range.end.to_point(&display_map);
19845 start..end
19846 })
19847 .collect::<Vec<_>>();
19848
19849 self.unfold_ranges(&ranges, true, true, cx);
19850 }
19851
19852 pub fn unfold_at(
19853 &mut self,
19854 buffer_row: MultiBufferRow,
19855 _window: &mut Window,
19856 cx: &mut Context<Self>,
19857 ) {
19858 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19859
19860 let intersection_range = Point::new(buffer_row.0, 0)
19861 ..Point::new(
19862 buffer_row.0,
19863 display_map.buffer_snapshot().line_len(buffer_row),
19864 );
19865
19866 let autoscroll = self
19867 .selections
19868 .all::<Point>(&display_map)
19869 .iter()
19870 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19871
19872 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19873 }
19874
19875 pub fn unfold_all(
19876 &mut self,
19877 _: &actions::UnfoldAll,
19878 _window: &mut Window,
19879 cx: &mut Context<Self>,
19880 ) {
19881 if self.buffer.read(cx).is_singleton() {
19882 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19883 self.unfold_ranges(
19884 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19885 true,
19886 true,
19887 cx,
19888 );
19889 } else {
19890 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19891 editor
19892 .update(cx, |editor, cx| {
19893 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19894 editor.unfold_buffer(buffer_id, cx);
19895 }
19896 })
19897 .ok();
19898 });
19899 }
19900 cx.emit(SearchEvent::ResultsCollapsedChanged(
19901 CollapseDirection::Expanded,
19902 ));
19903 }
19904
19905 pub fn fold_selected_ranges(
19906 &mut self,
19907 _: &FoldSelectedRanges,
19908 window: &mut Window,
19909 cx: &mut Context<Self>,
19910 ) {
19911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19912 let selections = self.selections.all_adjusted(&display_map);
19913 let ranges = selections
19914 .into_iter()
19915 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19916 .collect::<Vec<_>>();
19917 self.fold_creases(ranges, true, window, cx);
19918 }
19919
19920 pub fn fold_ranges<T: ToOffset + Clone>(
19921 &mut self,
19922 ranges: Vec<Range<T>>,
19923 auto_scroll: bool,
19924 window: &mut Window,
19925 cx: &mut Context<Self>,
19926 ) {
19927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19928 let ranges = ranges
19929 .into_iter()
19930 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19931 .collect::<Vec<_>>();
19932 self.fold_creases(ranges, auto_scroll, window, cx);
19933 }
19934
19935 pub fn fold_creases<T: ToOffset + Clone>(
19936 &mut self,
19937 creases: Vec<Crease<T>>,
19938 auto_scroll: bool,
19939 _window: &mut Window,
19940 cx: &mut Context<Self>,
19941 ) {
19942 if creases.is_empty() {
19943 return;
19944 }
19945
19946 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19947
19948 if auto_scroll {
19949 self.request_autoscroll(Autoscroll::fit(), cx);
19950 }
19951
19952 cx.notify();
19953
19954 self.scrollbar_marker_state.dirty = true;
19955 self.folds_did_change(cx);
19956 }
19957
19958 /// Removes any folds whose ranges intersect any of the given ranges.
19959 pub fn unfold_ranges<T: ToOffset + Clone>(
19960 &mut self,
19961 ranges: &[Range<T>],
19962 inclusive: bool,
19963 auto_scroll: bool,
19964 cx: &mut Context<Self>,
19965 ) {
19966 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19967 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19968 });
19969 self.folds_did_change(cx);
19970 }
19971
19972 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19973 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19974 return;
19975 }
19976
19977 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19978 self.display_map.update(cx, |display_map, cx| {
19979 display_map.fold_buffers([buffer_id], cx)
19980 });
19981
19982 let snapshot = self.display_snapshot(cx);
19983 self.selections.change_with(&snapshot, |selections| {
19984 selections.remove_selections_from_buffer(buffer_id);
19985 });
19986
19987 cx.emit(EditorEvent::BufferFoldToggled {
19988 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19989 folded: true,
19990 });
19991 cx.notify();
19992 }
19993
19994 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19995 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19996 return;
19997 }
19998 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19999 self.display_map.update(cx, |display_map, cx| {
20000 display_map.unfold_buffers([buffer_id], cx);
20001 });
20002 cx.emit(EditorEvent::BufferFoldToggled {
20003 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20004 folded: false,
20005 });
20006 cx.notify();
20007 }
20008
20009 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20010 self.display_map.read(cx).is_buffer_folded(buffer)
20011 }
20012
20013 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20014 self.display_map.read(cx).folded_buffers()
20015 }
20016
20017 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20018 self.display_map.update(cx, |display_map, cx| {
20019 display_map.disable_header_for_buffer(buffer_id, cx);
20020 });
20021 cx.notify();
20022 }
20023
20024 /// Removes any folds with the given ranges.
20025 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20026 &mut self,
20027 ranges: &[Range<T>],
20028 type_id: TypeId,
20029 auto_scroll: bool,
20030 cx: &mut Context<Self>,
20031 ) {
20032 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20033 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20034 });
20035 self.folds_did_change(cx);
20036 }
20037
20038 fn remove_folds_with<T: ToOffset + Clone>(
20039 &mut self,
20040 ranges: &[Range<T>],
20041 auto_scroll: bool,
20042 cx: &mut Context<Self>,
20043 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20044 ) {
20045 if ranges.is_empty() {
20046 return;
20047 }
20048
20049 let mut buffers_affected = HashSet::default();
20050 let multi_buffer = self.buffer().read(cx);
20051 for range in ranges {
20052 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20053 buffers_affected.insert(buffer.read(cx).remote_id());
20054 };
20055 }
20056
20057 self.display_map.update(cx, update);
20058
20059 if auto_scroll {
20060 self.request_autoscroll(Autoscroll::fit(), cx);
20061 }
20062
20063 cx.notify();
20064 self.scrollbar_marker_state.dirty = true;
20065 self.active_indent_guides_state.dirty = true;
20066 }
20067
20068 pub fn update_renderer_widths(
20069 &mut self,
20070 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20071 cx: &mut Context<Self>,
20072 ) -> bool {
20073 self.display_map
20074 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20075 }
20076
20077 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20078 self.display_map.read(cx).fold_placeholder.clone()
20079 }
20080
20081 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20082 self.buffer.update(cx, |buffer, cx| {
20083 buffer.set_all_diff_hunks_expanded(cx);
20084 });
20085 }
20086
20087 pub fn expand_all_diff_hunks(
20088 &mut self,
20089 _: &ExpandAllDiffHunks,
20090 _window: &mut Window,
20091 cx: &mut Context<Self>,
20092 ) {
20093 self.buffer.update(cx, |buffer, cx| {
20094 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20095 });
20096 }
20097
20098 pub fn collapse_all_diff_hunks(
20099 &mut self,
20100 _: &CollapseAllDiffHunks,
20101 _window: &mut Window,
20102 cx: &mut Context<Self>,
20103 ) {
20104 self.buffer.update(cx, |buffer, cx| {
20105 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20106 });
20107 }
20108
20109 pub fn toggle_selected_diff_hunks(
20110 &mut self,
20111 _: &ToggleSelectedDiffHunks,
20112 _window: &mut Window,
20113 cx: &mut Context<Self>,
20114 ) {
20115 let ranges: Vec<_> = self
20116 .selections
20117 .disjoint_anchors()
20118 .iter()
20119 .map(|s| s.range())
20120 .collect();
20121 self.toggle_diff_hunks_in_ranges(ranges, cx);
20122 }
20123
20124 pub fn diff_hunks_in_ranges<'a>(
20125 &'a self,
20126 ranges: &'a [Range<Anchor>],
20127 buffer: &'a MultiBufferSnapshot,
20128 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20129 ranges.iter().flat_map(move |range| {
20130 let end_excerpt_id = range.end.excerpt_id;
20131 let range = range.to_point(buffer);
20132 let mut peek_end = range.end;
20133 if range.end.row < buffer.max_row().0 {
20134 peek_end = Point::new(range.end.row + 1, 0);
20135 }
20136 buffer
20137 .diff_hunks_in_range(range.start..peek_end)
20138 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20139 })
20140 }
20141
20142 pub fn has_stageable_diff_hunks_in_ranges(
20143 &self,
20144 ranges: &[Range<Anchor>],
20145 snapshot: &MultiBufferSnapshot,
20146 ) -> bool {
20147 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20148 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20149 }
20150
20151 pub fn toggle_staged_selected_diff_hunks(
20152 &mut self,
20153 _: &::git::ToggleStaged,
20154 _: &mut Window,
20155 cx: &mut Context<Self>,
20156 ) {
20157 let snapshot = self.buffer.read(cx).snapshot(cx);
20158 let ranges: Vec<_> = self
20159 .selections
20160 .disjoint_anchors()
20161 .iter()
20162 .map(|s| s.range())
20163 .collect();
20164 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20165 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20166 }
20167
20168 pub fn set_render_diff_hunk_controls(
20169 &mut self,
20170 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20171 cx: &mut Context<Self>,
20172 ) {
20173 self.render_diff_hunk_controls = render_diff_hunk_controls;
20174 cx.notify();
20175 }
20176
20177 pub fn stage_and_next(
20178 &mut self,
20179 _: &::git::StageAndNext,
20180 window: &mut Window,
20181 cx: &mut Context<Self>,
20182 ) {
20183 self.do_stage_or_unstage_and_next(true, window, cx);
20184 }
20185
20186 pub fn unstage_and_next(
20187 &mut self,
20188 _: &::git::UnstageAndNext,
20189 window: &mut Window,
20190 cx: &mut Context<Self>,
20191 ) {
20192 self.do_stage_or_unstage_and_next(false, window, cx);
20193 }
20194
20195 pub fn stage_or_unstage_diff_hunks(
20196 &mut self,
20197 stage: bool,
20198 ranges: Vec<Range<Anchor>>,
20199 cx: &mut Context<Self>,
20200 ) {
20201 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20202 cx.spawn(async move |this, cx| {
20203 task.await?;
20204 this.update(cx, |this, cx| {
20205 let snapshot = this.buffer.read(cx).snapshot(cx);
20206 let chunk_by = this
20207 .diff_hunks_in_ranges(&ranges, &snapshot)
20208 .chunk_by(|hunk| hunk.buffer_id);
20209 for (buffer_id, hunks) in &chunk_by {
20210 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20211 }
20212 })
20213 })
20214 .detach_and_log_err(cx);
20215 }
20216
20217 fn save_buffers_for_ranges_if_needed(
20218 &mut self,
20219 ranges: &[Range<Anchor>],
20220 cx: &mut Context<Editor>,
20221 ) -> Task<Result<()>> {
20222 let multibuffer = self.buffer.read(cx);
20223 let snapshot = multibuffer.read(cx);
20224 let buffer_ids: HashSet<_> = ranges
20225 .iter()
20226 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20227 .collect();
20228 drop(snapshot);
20229
20230 let mut buffers = HashSet::default();
20231 for buffer_id in buffer_ids {
20232 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20233 let buffer = buffer_entity.read(cx);
20234 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20235 {
20236 buffers.insert(buffer_entity);
20237 }
20238 }
20239 }
20240
20241 if let Some(project) = &self.project {
20242 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20243 } else {
20244 Task::ready(Ok(()))
20245 }
20246 }
20247
20248 fn do_stage_or_unstage_and_next(
20249 &mut self,
20250 stage: bool,
20251 window: &mut Window,
20252 cx: &mut Context<Self>,
20253 ) {
20254 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20255
20256 if ranges.iter().any(|range| range.start != range.end) {
20257 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20258 return;
20259 }
20260
20261 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20262 let snapshot = self.snapshot(window, cx);
20263 let position = self
20264 .selections
20265 .newest::<Point>(&snapshot.display_snapshot)
20266 .head();
20267 let mut row = snapshot
20268 .buffer_snapshot()
20269 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20270 .find(|hunk| hunk.row_range.start.0 > position.row)
20271 .map(|hunk| hunk.row_range.start);
20272
20273 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20274 // Outside of the project diff editor, wrap around to the beginning.
20275 if !all_diff_hunks_expanded {
20276 row = row.or_else(|| {
20277 snapshot
20278 .buffer_snapshot()
20279 .diff_hunks_in_range(Point::zero()..position)
20280 .find(|hunk| hunk.row_range.end.0 < position.row)
20281 .map(|hunk| hunk.row_range.start)
20282 });
20283 }
20284
20285 if let Some(row) = row {
20286 let destination = Point::new(row.0, 0);
20287 let autoscroll = Autoscroll::center();
20288
20289 self.unfold_ranges(&[destination..destination], false, false, cx);
20290 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20291 s.select_ranges([destination..destination]);
20292 });
20293 }
20294 }
20295
20296 fn do_stage_or_unstage(
20297 &self,
20298 stage: bool,
20299 buffer_id: BufferId,
20300 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20301 cx: &mut App,
20302 ) -> Option<()> {
20303 let project = self.project()?;
20304 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20305 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20306 let buffer_snapshot = buffer.read(cx).snapshot();
20307 let file_exists = buffer_snapshot
20308 .file()
20309 .is_some_and(|file| file.disk_state().exists());
20310 diff.update(cx, |diff, cx| {
20311 diff.stage_or_unstage_hunks(
20312 stage,
20313 &hunks
20314 .map(|hunk| buffer_diff::DiffHunk {
20315 buffer_range: hunk.buffer_range,
20316 // We don't need to pass in word diffs here because they're only used for rendering and
20317 // this function changes internal state
20318 base_word_diffs: Vec::default(),
20319 buffer_word_diffs: Vec::default(),
20320 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20321 ..hunk.diff_base_byte_range.end.0,
20322 secondary_status: hunk.status.secondary,
20323 range: Point::zero()..Point::zero(), // unused
20324 })
20325 .collect::<Vec<_>>(),
20326 &buffer_snapshot,
20327 file_exists,
20328 cx,
20329 )
20330 });
20331 None
20332 }
20333
20334 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20335 let ranges: Vec<_> = self
20336 .selections
20337 .disjoint_anchors()
20338 .iter()
20339 .map(|s| s.range())
20340 .collect();
20341 self.buffer
20342 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20343 }
20344
20345 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20346 self.buffer.update(cx, |buffer, cx| {
20347 let ranges = vec![Anchor::min()..Anchor::max()];
20348 if !buffer.all_diff_hunks_expanded()
20349 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20350 {
20351 buffer.collapse_diff_hunks(ranges, cx);
20352 true
20353 } else {
20354 false
20355 }
20356 })
20357 }
20358
20359 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20360 if self.buffer.read(cx).all_diff_hunks_expanded() {
20361 return true;
20362 }
20363 let ranges = vec![Anchor::min()..Anchor::max()];
20364 self.buffer
20365 .read(cx)
20366 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20367 }
20368
20369 fn toggle_diff_hunks_in_ranges(
20370 &mut self,
20371 ranges: Vec<Range<Anchor>>,
20372 cx: &mut Context<Editor>,
20373 ) {
20374 self.buffer.update(cx, |buffer, cx| {
20375 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20376 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20377 })
20378 }
20379
20380 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20381 self.buffer.update(cx, |buffer, cx| {
20382 let snapshot = buffer.snapshot(cx);
20383 let excerpt_id = range.end.excerpt_id;
20384 let point_range = range.to_point(&snapshot);
20385 let expand = !buffer.single_hunk_is_expanded(range, cx);
20386 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20387 })
20388 }
20389
20390 pub(crate) fn apply_all_diff_hunks(
20391 &mut self,
20392 _: &ApplyAllDiffHunks,
20393 window: &mut Window,
20394 cx: &mut Context<Self>,
20395 ) {
20396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20397
20398 let buffers = self.buffer.read(cx).all_buffers();
20399 for branch_buffer in buffers {
20400 branch_buffer.update(cx, |branch_buffer, cx| {
20401 branch_buffer.merge_into_base(Vec::new(), cx);
20402 });
20403 }
20404
20405 if let Some(project) = self.project.clone() {
20406 self.save(
20407 SaveOptions {
20408 format: true,
20409 autosave: false,
20410 },
20411 project,
20412 window,
20413 cx,
20414 )
20415 .detach_and_log_err(cx);
20416 }
20417 }
20418
20419 pub(crate) fn apply_selected_diff_hunks(
20420 &mut self,
20421 _: &ApplyDiffHunk,
20422 window: &mut Window,
20423 cx: &mut Context<Self>,
20424 ) {
20425 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20426 let snapshot = self.snapshot(window, cx);
20427 let hunks = snapshot.hunks_for_ranges(
20428 self.selections
20429 .all(&snapshot.display_snapshot)
20430 .into_iter()
20431 .map(|selection| selection.range()),
20432 );
20433 let mut ranges_by_buffer = HashMap::default();
20434 self.transact(window, cx, |editor, _window, cx| {
20435 for hunk in hunks {
20436 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20437 ranges_by_buffer
20438 .entry(buffer.clone())
20439 .or_insert_with(Vec::new)
20440 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20441 }
20442 }
20443
20444 for (buffer, ranges) in ranges_by_buffer {
20445 buffer.update(cx, |buffer, cx| {
20446 buffer.merge_into_base(ranges, cx);
20447 });
20448 }
20449 });
20450
20451 if let Some(project) = self.project.clone() {
20452 self.save(
20453 SaveOptions {
20454 format: true,
20455 autosave: false,
20456 },
20457 project,
20458 window,
20459 cx,
20460 )
20461 .detach_and_log_err(cx);
20462 }
20463 }
20464
20465 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20466 if hovered != self.gutter_hovered {
20467 self.gutter_hovered = hovered;
20468 cx.notify();
20469 }
20470 }
20471
20472 pub fn insert_blocks(
20473 &mut self,
20474 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20475 autoscroll: Option<Autoscroll>,
20476 cx: &mut Context<Self>,
20477 ) -> Vec<CustomBlockId> {
20478 let blocks = self
20479 .display_map
20480 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20481 if let Some(autoscroll) = autoscroll {
20482 self.request_autoscroll(autoscroll, cx);
20483 }
20484 cx.notify();
20485 blocks
20486 }
20487
20488 pub fn resize_blocks(
20489 &mut self,
20490 heights: HashMap<CustomBlockId, u32>,
20491 autoscroll: Option<Autoscroll>,
20492 cx: &mut Context<Self>,
20493 ) {
20494 self.display_map
20495 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20496 if let Some(autoscroll) = autoscroll {
20497 self.request_autoscroll(autoscroll, cx);
20498 }
20499 cx.notify();
20500 }
20501
20502 pub fn replace_blocks(
20503 &mut self,
20504 renderers: HashMap<CustomBlockId, RenderBlock>,
20505 autoscroll: Option<Autoscroll>,
20506 cx: &mut Context<Self>,
20507 ) {
20508 self.display_map
20509 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20510 if let Some(autoscroll) = autoscroll {
20511 self.request_autoscroll(autoscroll, cx);
20512 }
20513 cx.notify();
20514 }
20515
20516 pub fn remove_blocks(
20517 &mut self,
20518 block_ids: HashSet<CustomBlockId>,
20519 autoscroll: Option<Autoscroll>,
20520 cx: &mut Context<Self>,
20521 ) {
20522 self.display_map.update(cx, |display_map, cx| {
20523 display_map.remove_blocks(block_ids, cx)
20524 });
20525 if let Some(autoscroll) = autoscroll {
20526 self.request_autoscroll(autoscroll, cx);
20527 }
20528 cx.notify();
20529 }
20530
20531 pub fn row_for_block(
20532 &self,
20533 block_id: CustomBlockId,
20534 cx: &mut Context<Self>,
20535 ) -> Option<DisplayRow> {
20536 self.display_map
20537 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20538 }
20539
20540 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20541 self.focused_block = Some(focused_block);
20542 }
20543
20544 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20545 self.focused_block.take()
20546 }
20547
20548 pub fn insert_creases(
20549 &mut self,
20550 creases: impl IntoIterator<Item = Crease<Anchor>>,
20551 cx: &mut Context<Self>,
20552 ) -> Vec<CreaseId> {
20553 self.display_map
20554 .update(cx, |map, cx| map.insert_creases(creases, cx))
20555 }
20556
20557 pub fn remove_creases(
20558 &mut self,
20559 ids: impl IntoIterator<Item = CreaseId>,
20560 cx: &mut Context<Self>,
20561 ) -> Vec<(CreaseId, Range<Anchor>)> {
20562 self.display_map
20563 .update(cx, |map, cx| map.remove_creases(ids, cx))
20564 }
20565
20566 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20567 self.display_map
20568 .update(cx, |map, cx| map.snapshot(cx))
20569 .longest_row()
20570 }
20571
20572 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20573 self.display_map
20574 .update(cx, |map, cx| map.snapshot(cx))
20575 .max_point()
20576 }
20577
20578 pub fn text(&self, cx: &App) -> String {
20579 self.buffer.read(cx).read(cx).text()
20580 }
20581
20582 pub fn is_empty(&self, cx: &App) -> bool {
20583 self.buffer.read(cx).read(cx).is_empty()
20584 }
20585
20586 pub fn text_option(&self, cx: &App) -> Option<String> {
20587 let text = self.text(cx);
20588 let text = text.trim();
20589
20590 if text.is_empty() {
20591 return None;
20592 }
20593
20594 Some(text.to_string())
20595 }
20596
20597 pub fn set_text(
20598 &mut self,
20599 text: impl Into<Arc<str>>,
20600 window: &mut Window,
20601 cx: &mut Context<Self>,
20602 ) {
20603 self.transact(window, cx, |this, _, cx| {
20604 this.buffer
20605 .read(cx)
20606 .as_singleton()
20607 .expect("you can only call set_text on editors for singleton buffers")
20608 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20609 });
20610 }
20611
20612 pub fn display_text(&self, cx: &mut App) -> String {
20613 self.display_map
20614 .update(cx, |map, cx| map.snapshot(cx))
20615 .text()
20616 }
20617
20618 fn create_minimap(
20619 &self,
20620 minimap_settings: MinimapSettings,
20621 window: &mut Window,
20622 cx: &mut Context<Self>,
20623 ) -> Option<Entity<Self>> {
20624 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20625 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20626 }
20627
20628 fn initialize_new_minimap(
20629 &self,
20630 minimap_settings: MinimapSettings,
20631 window: &mut Window,
20632 cx: &mut Context<Self>,
20633 ) -> Entity<Self> {
20634 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20635 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20636
20637 let mut minimap = Editor::new_internal(
20638 EditorMode::Minimap {
20639 parent: cx.weak_entity(),
20640 },
20641 self.buffer.clone(),
20642 None,
20643 Some(self.display_map.clone()),
20644 window,
20645 cx,
20646 );
20647 minimap.scroll_manager.clone_state(&self.scroll_manager);
20648 minimap.set_text_style_refinement(TextStyleRefinement {
20649 font_size: Some(MINIMAP_FONT_SIZE),
20650 font_weight: Some(MINIMAP_FONT_WEIGHT),
20651 font_family: Some(MINIMAP_FONT_FAMILY),
20652 ..Default::default()
20653 });
20654 minimap.update_minimap_configuration(minimap_settings, cx);
20655 cx.new(|_| minimap)
20656 }
20657
20658 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20659 let current_line_highlight = minimap_settings
20660 .current_line_highlight
20661 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20662 self.set_current_line_highlight(Some(current_line_highlight));
20663 }
20664
20665 pub fn minimap(&self) -> Option<&Entity<Self>> {
20666 self.minimap
20667 .as_ref()
20668 .filter(|_| self.minimap_visibility.visible())
20669 }
20670
20671 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20672 let mut wrap_guides = smallvec![];
20673
20674 if self.show_wrap_guides == Some(false) {
20675 return wrap_guides;
20676 }
20677
20678 let settings = self.buffer.read(cx).language_settings(cx);
20679 if settings.show_wrap_guides {
20680 match self.soft_wrap_mode(cx) {
20681 SoftWrap::Column(soft_wrap) => {
20682 wrap_guides.push((soft_wrap as usize, true));
20683 }
20684 SoftWrap::Bounded(soft_wrap) => {
20685 wrap_guides.push((soft_wrap as usize, true));
20686 }
20687 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20688 }
20689 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20690 }
20691
20692 wrap_guides
20693 }
20694
20695 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20696 let settings = self.buffer.read(cx).language_settings(cx);
20697 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20698 match mode {
20699 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20700 SoftWrap::None
20701 }
20702 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20703 language_settings::SoftWrap::PreferredLineLength => {
20704 SoftWrap::Column(settings.preferred_line_length)
20705 }
20706 language_settings::SoftWrap::Bounded => {
20707 SoftWrap::Bounded(settings.preferred_line_length)
20708 }
20709 }
20710 }
20711
20712 pub fn set_soft_wrap_mode(
20713 &mut self,
20714 mode: language_settings::SoftWrap,
20715
20716 cx: &mut Context<Self>,
20717 ) {
20718 self.soft_wrap_mode_override = Some(mode);
20719 cx.notify();
20720 }
20721
20722 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20723 self.hard_wrap = hard_wrap;
20724 cx.notify();
20725 }
20726
20727 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20728 self.text_style_refinement = Some(style);
20729 }
20730
20731 /// called by the Element so we know what style we were most recently rendered with.
20732 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20733 // We intentionally do not inform the display map about the minimap style
20734 // so that wrapping is not recalculated and stays consistent for the editor
20735 // and its linked minimap.
20736 if !self.mode.is_minimap() {
20737 let font = style.text.font();
20738 let font_size = style.text.font_size.to_pixels(window.rem_size());
20739 let display_map = self
20740 .placeholder_display_map
20741 .as_ref()
20742 .filter(|_| self.is_empty(cx))
20743 .unwrap_or(&self.display_map);
20744
20745 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20746 }
20747 self.style = Some(style);
20748 }
20749
20750 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20751 if self.style.is_none() {
20752 self.style = Some(self.create_style(cx));
20753 }
20754 self.style.as_ref().unwrap()
20755 }
20756
20757 // Called by the element. This method is not designed to be called outside of the editor
20758 // element's layout code because it does not notify when rewrapping is computed synchronously.
20759 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20760 if self.is_empty(cx) {
20761 self.placeholder_display_map
20762 .as_ref()
20763 .map_or(false, |display_map| {
20764 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20765 })
20766 } else {
20767 self.display_map
20768 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20769 }
20770 }
20771
20772 pub fn set_soft_wrap(&mut self) {
20773 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20774 }
20775
20776 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20777 if self.soft_wrap_mode_override.is_some() {
20778 self.soft_wrap_mode_override.take();
20779 } else {
20780 let soft_wrap = match self.soft_wrap_mode(cx) {
20781 SoftWrap::GitDiff => return,
20782 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20783 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20784 language_settings::SoftWrap::None
20785 }
20786 };
20787 self.soft_wrap_mode_override = Some(soft_wrap);
20788 }
20789 cx.notify();
20790 }
20791
20792 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20793 let Some(workspace) = self.workspace() else {
20794 return;
20795 };
20796 let fs = workspace.read(cx).app_state().fs.clone();
20797 let current_show = TabBarSettings::get_global(cx).show;
20798 update_settings_file(fs, cx, move |setting, _| {
20799 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20800 });
20801 }
20802
20803 pub fn toggle_indent_guides(
20804 &mut self,
20805 _: &ToggleIndentGuides,
20806 _: &mut Window,
20807 cx: &mut Context<Self>,
20808 ) {
20809 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20810 self.buffer
20811 .read(cx)
20812 .language_settings(cx)
20813 .indent_guides
20814 .enabled
20815 });
20816 self.show_indent_guides = Some(!currently_enabled);
20817 cx.notify();
20818 }
20819
20820 fn should_show_indent_guides(&self) -> Option<bool> {
20821 self.show_indent_guides
20822 }
20823
20824 pub fn disable_indent_guides_for_buffer(
20825 &mut self,
20826 buffer_id: BufferId,
20827 cx: &mut Context<Self>,
20828 ) {
20829 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20830 cx.notify();
20831 }
20832
20833 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20834 self.buffers_with_disabled_indent_guides
20835 .contains(&buffer_id)
20836 }
20837
20838 pub fn toggle_line_numbers(
20839 &mut self,
20840 _: &ToggleLineNumbers,
20841 _: &mut Window,
20842 cx: &mut Context<Self>,
20843 ) {
20844 let mut editor_settings = EditorSettings::get_global(cx).clone();
20845 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20846 EditorSettings::override_global(editor_settings, cx);
20847 }
20848
20849 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20850 if let Some(show_line_numbers) = self.show_line_numbers {
20851 return show_line_numbers;
20852 }
20853 EditorSettings::get_global(cx).gutter.line_numbers
20854 }
20855
20856 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20857 match (
20858 self.use_relative_line_numbers,
20859 EditorSettings::get_global(cx).relative_line_numbers,
20860 ) {
20861 (None, setting) => setting,
20862 (Some(false), _) => RelativeLineNumbers::Disabled,
20863 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20864 (Some(true), _) => RelativeLineNumbers::Enabled,
20865 }
20866 }
20867
20868 pub fn toggle_relative_line_numbers(
20869 &mut self,
20870 _: &ToggleRelativeLineNumbers,
20871 _: &mut Window,
20872 cx: &mut Context<Self>,
20873 ) {
20874 let is_relative = self.relative_line_numbers(cx);
20875 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20876 }
20877
20878 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20879 self.use_relative_line_numbers = is_relative;
20880 cx.notify();
20881 }
20882
20883 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20884 self.show_gutter = show_gutter;
20885 cx.notify();
20886 }
20887
20888 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20889 self.show_scrollbars = ScrollbarAxes {
20890 horizontal: show,
20891 vertical: show,
20892 };
20893 cx.notify();
20894 }
20895
20896 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20897 self.show_scrollbars.vertical = show;
20898 cx.notify();
20899 }
20900
20901 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20902 self.show_scrollbars.horizontal = show;
20903 cx.notify();
20904 }
20905
20906 pub fn set_minimap_visibility(
20907 &mut self,
20908 minimap_visibility: MinimapVisibility,
20909 window: &mut Window,
20910 cx: &mut Context<Self>,
20911 ) {
20912 if self.minimap_visibility != minimap_visibility {
20913 if minimap_visibility.visible() && self.minimap.is_none() {
20914 let minimap_settings = EditorSettings::get_global(cx).minimap;
20915 self.minimap =
20916 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20917 }
20918 self.minimap_visibility = minimap_visibility;
20919 cx.notify();
20920 }
20921 }
20922
20923 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20924 self.set_show_scrollbars(false, cx);
20925 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20926 }
20927
20928 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20929 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20930 }
20931
20932 /// Normally the text in full mode and auto height editors is padded on the
20933 /// left side by roughly half a character width for improved hit testing.
20934 ///
20935 /// Use this method to disable this for cases where this is not wanted (e.g.
20936 /// if you want to align the editor text with some other text above or below)
20937 /// or if you want to add this padding to single-line editors.
20938 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20939 self.offset_content = offset_content;
20940 cx.notify();
20941 }
20942
20943 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20944 self.show_line_numbers = Some(show_line_numbers);
20945 cx.notify();
20946 }
20947
20948 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20949 self.disable_expand_excerpt_buttons = true;
20950 cx.notify();
20951 }
20952
20953 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
20954 self.delegate_expand_excerpts = delegate;
20955 }
20956
20957 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20958 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20959 cx.notify();
20960 }
20961
20962 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20963 self.show_code_actions = Some(show_code_actions);
20964 cx.notify();
20965 }
20966
20967 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20968 self.show_runnables = Some(show_runnables);
20969 cx.notify();
20970 }
20971
20972 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20973 self.show_breakpoints = Some(show_breakpoints);
20974 cx.notify();
20975 }
20976
20977 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
20978 self.show_diff_review_button = show;
20979 cx.notify();
20980 }
20981
20982 pub fn show_diff_review_button(&self) -> bool {
20983 self.show_diff_review_button
20984 }
20985
20986 pub fn render_diff_review_button(
20987 &self,
20988 display_row: DisplayRow,
20989 width: Pixels,
20990 cx: &mut Context<Self>,
20991 ) -> impl IntoElement {
20992 let text_color = cx.theme().colors().text;
20993 let icon_color = cx.theme().colors().icon_accent;
20994
20995 h_flex()
20996 .id("diff_review_button")
20997 .cursor_pointer()
20998 .w(width - px(1.))
20999 .h(relative(0.9))
21000 .justify_center()
21001 .rounded_sm()
21002 .border_1()
21003 .border_color(text_color.opacity(0.1))
21004 .bg(text_color.opacity(0.15))
21005 .hover(|s| {
21006 s.bg(icon_color.opacity(0.4))
21007 .border_color(icon_color.opacity(0.5))
21008 })
21009 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21010 .tooltip(Tooltip::text("Add Review"))
21011 .on_click(cx.listener(move |editor, _event: &ClickEvent, window, cx| {
21012 editor.show_diff_review_overlay(display_row, window, cx);
21013 }))
21014 }
21015
21016 /// Calculates the appropriate block height for the diff review overlay.
21017 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21018 /// and 2 lines per comment when expanded.
21019 fn calculate_overlay_height(
21020 &self,
21021 hunk_key: &DiffHunkKey,
21022 comments_expanded: bool,
21023 snapshot: &MultiBufferSnapshot,
21024 ) -> u32 {
21025 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21026 let base_height: u32 = 2; // Input row with avatar and buttons
21027
21028 if comment_count == 0 {
21029 base_height
21030 } else if comments_expanded {
21031 // Header (1 line) + 2 lines per comment
21032 base_height + 1 + (comment_count as u32 * 2)
21033 } else {
21034 // Just header when collapsed
21035 base_height + 1
21036 }
21037 }
21038
21039 pub fn show_diff_review_overlay(
21040 &mut self,
21041 display_row: DisplayRow,
21042 window: &mut Window,
21043 cx: &mut Context<Self>,
21044 ) {
21045 // Check if there's already an overlay for the same hunk - if so, just focus it
21046 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21047 let editor_snapshot = self.snapshot(window, cx);
21048 let display_point = DisplayPoint::new(display_row, 0);
21049 let buffer_point = editor_snapshot
21050 .display_snapshot
21051 .display_point_to_point(display_point, Bias::Left);
21052
21053 // Compute the hunk key for this display row
21054 let file_path = buffer_snapshot
21055 .file_at(Point::new(buffer_point.row, 0))
21056 .map(|file: &Arc<dyn language::File>| file.path().clone())
21057 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21058 let hunk_start_anchor = buffer_snapshot.anchor_before(Point::new(buffer_point.row, 0));
21059 let new_hunk_key = DiffHunkKey {
21060 file_path,
21061 hunk_start_anchor,
21062 };
21063
21064 // Check if we already have an overlay for this hunk
21065 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21066 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21067 }) {
21068 // Just focus the existing overlay's prompt editor
21069 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21070 window.focus(&focus_handle, cx);
21071 return;
21072 }
21073
21074 // Dismiss overlays that have no comments for their hunks
21075 self.dismiss_overlays_without_comments(cx);
21076
21077 // Get the current user's avatar URI from the project's user_store
21078 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21079 let user_store = project.read(cx).user_store();
21080 user_store
21081 .read(cx)
21082 .current_user()
21083 .map(|user| user.avatar_uri.clone())
21084 });
21085
21086 // Create anchor at the end of the row so the block appears immediately below it
21087 let line_len = buffer_snapshot.line_len(MultiBufferRow(buffer_point.row));
21088 let anchor = buffer_snapshot.anchor_after(Point::new(buffer_point.row, line_len));
21089
21090 // Use the hunk key we already computed
21091 let hunk_key = new_hunk_key;
21092
21093 // Create the prompt editor for the review input
21094 let prompt_editor = cx.new(|cx| {
21095 let mut editor = Editor::single_line(window, cx);
21096 editor.set_placeholder_text("Add a review comment...", window, cx);
21097 editor
21098 });
21099
21100 // Register the Newline action on the prompt editor to submit the review
21101 let parent_editor = cx.entity().downgrade();
21102 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21103 prompt_editor.register_action({
21104 let parent_editor = parent_editor.clone();
21105 move |_: &crate::actions::Newline, window, cx| {
21106 if let Some(editor) = parent_editor.upgrade() {
21107 editor.update(cx, |editor, cx| {
21108 editor.submit_diff_review_comment(window, cx);
21109 });
21110 }
21111 }
21112 })
21113 });
21114
21115 // Calculate initial height based on existing comments for this hunk
21116 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21117
21118 // Create the overlay block
21119 let prompt_editor_for_render = prompt_editor.clone();
21120 let hunk_key_for_render = hunk_key.clone();
21121 let editor_handle = cx.entity().downgrade();
21122 let block = BlockProperties {
21123 style: BlockStyle::Sticky,
21124 placement: BlockPlacement::Below(anchor),
21125 height: Some(initial_height),
21126 render: Arc::new(move |cx| {
21127 Self::render_diff_review_overlay(
21128 &prompt_editor_for_render,
21129 &hunk_key_for_render,
21130 &editor_handle,
21131 cx,
21132 )
21133 }),
21134 priority: 0,
21135 };
21136
21137 let block_ids = self.insert_blocks([block], None, cx);
21138 let Some(block_id) = block_ids.into_iter().next() else {
21139 log::error!("Failed to insert diff review overlay block");
21140 return;
21141 };
21142
21143 self.diff_review_overlays.push(DiffReviewOverlay {
21144 display_row,
21145 block_id,
21146 prompt_editor: prompt_editor.clone(),
21147 hunk_key,
21148 comments_expanded: true,
21149 inline_edit_editors: HashMap::default(),
21150 inline_edit_subscriptions: HashMap::default(),
21151 user_avatar_uri,
21152 _subscription: subscription,
21153 });
21154
21155 // Focus the prompt editor
21156 let focus_handle = prompt_editor.focus_handle(cx);
21157 window.focus(&focus_handle, cx);
21158
21159 cx.notify();
21160 }
21161
21162 /// Dismisses all diff review overlays.
21163 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21164 if self.diff_review_overlays.is_empty() {
21165 return;
21166 }
21167 let block_ids: HashSet<_> = self
21168 .diff_review_overlays
21169 .drain(..)
21170 .map(|overlay| overlay.block_id)
21171 .collect();
21172 self.remove_blocks(block_ids, None, cx);
21173 cx.notify();
21174 }
21175
21176 /// Dismisses overlays that have no comments stored for their hunks.
21177 /// Keeps overlays that have at least one comment.
21178 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21179 let snapshot = self.buffer.read(cx).snapshot(cx);
21180
21181 // First, compute which overlays have comments (to avoid borrow issues with retain)
21182 let overlays_with_comments: Vec<bool> = self
21183 .diff_review_overlays
21184 .iter()
21185 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21186 .collect();
21187
21188 // Now collect block IDs to remove and retain overlays
21189 let mut block_ids_to_remove = HashSet::default();
21190 let mut index = 0;
21191 self.diff_review_overlays.retain(|overlay| {
21192 let has_comments = overlays_with_comments[index];
21193 index += 1;
21194 if !has_comments {
21195 block_ids_to_remove.insert(overlay.block_id);
21196 }
21197 has_comments
21198 });
21199
21200 if !block_ids_to_remove.is_empty() {
21201 self.remove_blocks(block_ids_to_remove, None, cx);
21202 cx.notify();
21203 }
21204 }
21205
21206 /// Refreshes the diff review overlay block to update its height and render function.
21207 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21208 fn refresh_diff_review_overlay_height(
21209 &mut self,
21210 hunk_key: &DiffHunkKey,
21211 _window: &mut Window,
21212 cx: &mut Context<Self>,
21213 ) {
21214 // Extract all needed data from overlay first to avoid borrow conflicts
21215 let snapshot = self.buffer.read(cx).snapshot(cx);
21216 let (comments_expanded, block_id, prompt_editor) = {
21217 let Some(overlay) = self
21218 .diff_review_overlays
21219 .iter()
21220 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21221 else {
21222 return;
21223 };
21224
21225 (
21226 overlay.comments_expanded,
21227 overlay.block_id,
21228 overlay.prompt_editor.clone(),
21229 )
21230 };
21231
21232 // Calculate new height
21233 let snapshot = self.buffer.read(cx).snapshot(cx);
21234 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21235
21236 // Update the block height using resize_blocks (avoids flicker)
21237 let mut heights = HashMap::default();
21238 heights.insert(block_id, new_height);
21239 self.resize_blocks(heights, None, cx);
21240
21241 // Update the render function using replace_blocks (avoids flicker)
21242 let hunk_key_for_render = hunk_key.clone();
21243 let editor_handle = cx.entity().downgrade();
21244 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21245 Arc::new(move |cx| {
21246 Self::render_diff_review_overlay(
21247 &prompt_editor,
21248 &hunk_key_for_render,
21249 &editor_handle,
21250 cx,
21251 )
21252 });
21253
21254 let mut renderers = HashMap::default();
21255 renderers.insert(block_id, render);
21256 self.replace_blocks(renderers, None, cx);
21257 }
21258
21259 /// Action handler for SubmitDiffReviewComment.
21260 pub fn submit_diff_review_comment_action(
21261 &mut self,
21262 _: &SubmitDiffReviewComment,
21263 window: &mut Window,
21264 cx: &mut Context<Self>,
21265 ) {
21266 self.submit_diff_review_comment(window, cx);
21267 }
21268
21269 /// Stores the diff review comment locally.
21270 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21271 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21272 // Find the overlay that currently has focus
21273 let overlay_index = self
21274 .diff_review_overlays
21275 .iter()
21276 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21277 let Some(overlay_index) = overlay_index else {
21278 return;
21279 };
21280 let overlay = &self.diff_review_overlays[overlay_index];
21281
21282 // Get the comment text from the prompt editor
21283 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21284
21285 // Don't submit if the comment is empty
21286 if comment_text.is_empty() {
21287 return;
21288 }
21289
21290 // Get the display row and hunk key
21291 let display_row = overlay.display_row;
21292 let hunk_key = overlay.hunk_key.clone();
21293
21294 // Convert to buffer position for anchors
21295 let snapshot = self.snapshot(window, cx);
21296 let display_point = DisplayPoint::new(display_row, 0);
21297 let buffer_point = snapshot
21298 .display_snapshot
21299 .display_point_to_point(display_point, Bias::Left);
21300
21301 // Get the line range
21302 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21303 let line_start = Point::new(buffer_point.row, 0);
21304 let line_end = Point::new(
21305 buffer_point.row,
21306 buffer_snapshot.line_len(MultiBufferRow(buffer_point.row)),
21307 );
21308
21309 // Create anchors for the selection
21310 let anchor_start = buffer_snapshot.anchor_after(line_start);
21311 let anchor_end = buffer_snapshot.anchor_before(line_end);
21312
21313 // Store the comment locally
21314 self.add_review_comment(
21315 hunk_key.clone(),
21316 comment_text,
21317 display_row,
21318 anchor_start..anchor_end,
21319 cx,
21320 );
21321
21322 // Clear the prompt editor but keep the overlay open
21323 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21324 overlay.prompt_editor.update(cx, |editor, cx| {
21325 editor.clear(window, cx);
21326 });
21327 }
21328
21329 // Refresh the overlay to update the block height for the new comment
21330 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21331
21332 cx.notify();
21333 }
21334
21335 /// Returns the prompt editor for the diff review overlay, if one is active.
21336 /// This is primarily used for testing.
21337 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21338 self.diff_review_overlays
21339 .first()
21340 .map(|overlay| &overlay.prompt_editor)
21341 }
21342
21343 /// Returns the display row for the first diff review overlay, if one is active.
21344 pub fn diff_review_display_row(&self) -> Option<DisplayRow> {
21345 self.diff_review_overlays
21346 .first()
21347 .map(|overlay| overlay.display_row)
21348 }
21349
21350 /// Sets whether the comments section is expanded in the diff review overlay.
21351 /// This is primarily used for testing.
21352 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21353 for overlay in &mut self.diff_review_overlays {
21354 overlay.comments_expanded = expanded;
21355 }
21356 cx.notify();
21357 }
21358
21359 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21360 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21361 a.file_path == b.file_path
21362 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21363 }
21364
21365 /// Returns comments for a specific hunk, ordered by creation time.
21366 pub fn comments_for_hunk<'a>(
21367 &'a self,
21368 key: &DiffHunkKey,
21369 snapshot: &MultiBufferSnapshot,
21370 ) -> &'a [StoredReviewComment] {
21371 let key_point = key.hunk_start_anchor.to_point(snapshot);
21372 self.stored_review_comments
21373 .iter()
21374 .find(|(k, _)| {
21375 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21376 })
21377 .map(|(_, comments)| comments.as_slice())
21378 .unwrap_or(&[])
21379 }
21380
21381 /// Returns the total count of stored review comments across all hunks.
21382 pub fn total_review_comment_count(&self) -> usize {
21383 self.stored_review_comments
21384 .iter()
21385 .map(|(_, v)| v.len())
21386 .sum()
21387 }
21388
21389 /// Returns the count of comments for a specific hunk.
21390 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21391 let key_point = key.hunk_start_anchor.to_point(snapshot);
21392 self.stored_review_comments
21393 .iter()
21394 .find(|(k, _)| {
21395 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21396 })
21397 .map(|(_, v)| v.len())
21398 .unwrap_or(0)
21399 }
21400
21401 /// Adds a new review comment to a specific hunk.
21402 pub fn add_review_comment(
21403 &mut self,
21404 hunk_key: DiffHunkKey,
21405 comment: String,
21406 display_row: DisplayRow,
21407 anchor_range: Range<Anchor>,
21408 cx: &mut Context<Self>,
21409 ) -> usize {
21410 let id = self.next_review_comment_id;
21411 self.next_review_comment_id += 1;
21412
21413 let stored_comment = StoredReviewComment::new(id, comment, display_row, anchor_range);
21414
21415 let snapshot = self.buffer.read(cx).snapshot(cx);
21416 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21417
21418 // Find existing entry for this hunk or add a new one
21419 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21420 k.file_path == hunk_key.file_path
21421 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21422 }) {
21423 comments.push(stored_comment);
21424 } else {
21425 self.stored_review_comments
21426 .push((hunk_key, vec![stored_comment]));
21427 }
21428
21429 cx.emit(EditorEvent::ReviewCommentsChanged {
21430 total_count: self.total_review_comment_count(),
21431 });
21432 cx.notify();
21433 id
21434 }
21435
21436 /// Removes a review comment by ID from any hunk.
21437 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21438 for (_, comments) in self.stored_review_comments.iter_mut() {
21439 if let Some(index) = comments.iter().position(|c| c.id == id) {
21440 comments.remove(index);
21441 cx.emit(EditorEvent::ReviewCommentsChanged {
21442 total_count: self.total_review_comment_count(),
21443 });
21444 cx.notify();
21445 return true;
21446 }
21447 }
21448 false
21449 }
21450
21451 /// Updates a review comment's text by ID.
21452 pub fn update_review_comment(
21453 &mut self,
21454 id: usize,
21455 new_comment: String,
21456 cx: &mut Context<Self>,
21457 ) -> bool {
21458 for (_, comments) in self.stored_review_comments.iter_mut() {
21459 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21460 comment.comment = new_comment;
21461 comment.is_editing = false;
21462 cx.emit(EditorEvent::ReviewCommentsChanged {
21463 total_count: self.total_review_comment_count(),
21464 });
21465 cx.notify();
21466 return true;
21467 }
21468 }
21469 false
21470 }
21471
21472 /// Sets a comment's editing state.
21473 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21474 for (_, comments) in self.stored_review_comments.iter_mut() {
21475 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21476 comment.is_editing = is_editing;
21477 cx.notify();
21478 return;
21479 }
21480 }
21481 }
21482
21483 /// Takes all stored comments from all hunks, clearing the storage.
21484 /// Returns a Vec of (hunk_key, comments) pairs.
21485 pub fn take_all_review_comments(
21486 &mut self,
21487 cx: &mut Context<Self>,
21488 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21489 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21490 self.dismiss_all_diff_review_overlays(cx);
21491 let comments = std::mem::take(&mut self.stored_review_comments);
21492 // Reset the ID counter since all comments have been taken
21493 self.next_review_comment_id = 0;
21494 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21495 cx.notify();
21496 comments
21497 }
21498
21499 /// Removes review comments whose anchors are no longer valid or whose
21500 /// associated diff hunks no longer exist.
21501 ///
21502 /// This should be called when the buffer changes to prevent orphaned comments
21503 /// from accumulating.
21504 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21505 let snapshot = self.buffer.read(cx).snapshot(cx);
21506 let original_count = self.total_review_comment_count();
21507
21508 // Remove comments with invalid hunk anchors
21509 self.stored_review_comments
21510 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21511
21512 // Also clean up individual comments with invalid anchor ranges
21513 for (_, comments) in &mut self.stored_review_comments {
21514 comments.retain(|comment| {
21515 comment.anchor_range.start.is_valid(&snapshot)
21516 && comment.anchor_range.end.is_valid(&snapshot)
21517 });
21518 }
21519
21520 // Remove empty hunk entries
21521 self.stored_review_comments
21522 .retain(|(_, comments)| !comments.is_empty());
21523
21524 let new_count = self.total_review_comment_count();
21525 if new_count != original_count {
21526 cx.emit(EditorEvent::ReviewCommentsChanged {
21527 total_count: new_count,
21528 });
21529 cx.notify();
21530 }
21531 }
21532
21533 /// Toggles the expanded state of the comments section in the overlay.
21534 pub fn toggle_review_comments_expanded(
21535 &mut self,
21536 _: &ToggleReviewCommentsExpanded,
21537 window: &mut Window,
21538 cx: &mut Context<Self>,
21539 ) {
21540 // Find the overlay that currently has focus, or use the first one
21541 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21542 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21543 overlay.comments_expanded = !overlay.comments_expanded;
21544 Some(overlay.hunk_key.clone())
21545 } else {
21546 None
21547 }
21548 });
21549
21550 // If no focused overlay found, toggle the first one
21551 let hunk_key = overlay_info.or_else(|| {
21552 self.diff_review_overlays.first_mut().map(|overlay| {
21553 overlay.comments_expanded = !overlay.comments_expanded;
21554 overlay.hunk_key.clone()
21555 })
21556 });
21557
21558 if let Some(hunk_key) = hunk_key {
21559 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21560 cx.notify();
21561 }
21562 }
21563
21564 /// Handles the EditReviewComment action - sets a comment into editing mode.
21565 pub fn edit_review_comment(
21566 &mut self,
21567 action: &EditReviewComment,
21568 window: &mut Window,
21569 cx: &mut Context<Self>,
21570 ) {
21571 let comment_id = action.id;
21572
21573 // Set the comment to editing mode
21574 self.set_comment_editing(comment_id, true, cx);
21575
21576 // Find the overlay that contains this comment and create an inline editor if needed
21577 // First, find which hunk this comment belongs to
21578 let hunk_key = self
21579 .stored_review_comments
21580 .iter()
21581 .find_map(|(key, comments)| {
21582 if comments.iter().any(|c| c.id == comment_id) {
21583 Some(key.clone())
21584 } else {
21585 None
21586 }
21587 });
21588
21589 let snapshot = self.buffer.read(cx).snapshot(cx);
21590 if let Some(hunk_key) = hunk_key {
21591 if let Some(overlay) = self
21592 .diff_review_overlays
21593 .iter_mut()
21594 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21595 {
21596 if let std::collections::hash_map::Entry::Vacant(entry) =
21597 overlay.inline_edit_editors.entry(comment_id)
21598 {
21599 // Find the comment text
21600 let comment_text = self
21601 .stored_review_comments
21602 .iter()
21603 .flat_map(|(_, comments)| comments)
21604 .find(|c| c.id == comment_id)
21605 .map(|c| c.comment.clone())
21606 .unwrap_or_default();
21607
21608 // Create inline editor
21609 let parent_editor = cx.entity().downgrade();
21610 let inline_editor = cx.new(|cx| {
21611 let mut editor = Editor::single_line(window, cx);
21612 editor.set_text(&*comment_text, window, cx);
21613 // Select all text for easy replacement
21614 editor.select_all(&crate::actions::SelectAll, window, cx);
21615 editor
21616 });
21617
21618 // Register the Newline action to confirm the edit
21619 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
21620 inline_editor.register_action({
21621 let parent_editor = parent_editor.clone();
21622 move |_: &crate::actions::Newline, window, cx| {
21623 if let Some(editor) = parent_editor.upgrade() {
21624 editor.update(cx, |editor, cx| {
21625 editor.confirm_edit_review_comment(comment_id, window, cx);
21626 });
21627 }
21628 }
21629 })
21630 });
21631
21632 // Store the subscription to keep the action handler alive
21633 overlay
21634 .inline_edit_subscriptions
21635 .insert(comment_id, subscription);
21636
21637 // Focus the inline editor
21638 let focus_handle = inline_editor.focus_handle(cx);
21639 window.focus(&focus_handle, cx);
21640
21641 entry.insert(inline_editor);
21642 }
21643 }
21644 }
21645
21646 cx.notify();
21647 }
21648
21649 /// Confirms an inline edit of a review comment.
21650 pub fn confirm_edit_review_comment(
21651 &mut self,
21652 comment_id: usize,
21653 _window: &mut Window,
21654 cx: &mut Context<Self>,
21655 ) {
21656 // Get the new text from the inline editor
21657 // Find the overlay containing this comment's inline editor
21658 let snapshot = self.buffer.read(cx).snapshot(cx);
21659 let hunk_key = self
21660 .stored_review_comments
21661 .iter()
21662 .find_map(|(key, comments)| {
21663 if comments.iter().any(|c| c.id == comment_id) {
21664 Some(key.clone())
21665 } else {
21666 None
21667 }
21668 });
21669
21670 let new_text = hunk_key
21671 .as_ref()
21672 .and_then(|hunk_key| {
21673 self.diff_review_overlays
21674 .iter()
21675 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21676 })
21677 .as_ref()
21678 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
21679 .map(|editor| editor.read(cx).text(cx).trim().to_string());
21680
21681 if let Some(new_text) = new_text {
21682 if !new_text.is_empty() {
21683 self.update_review_comment(comment_id, new_text, cx);
21684 }
21685 }
21686
21687 // Remove the inline editor and its subscription
21688 if let Some(hunk_key) = hunk_key {
21689 if let Some(overlay) = self
21690 .diff_review_overlays
21691 .iter_mut()
21692 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21693 {
21694 overlay.inline_edit_editors.remove(&comment_id);
21695 overlay.inline_edit_subscriptions.remove(&comment_id);
21696 }
21697 }
21698
21699 // Clear editing state
21700 self.set_comment_editing(comment_id, false, cx);
21701 }
21702
21703 /// Cancels an inline edit of a review comment.
21704 pub fn cancel_edit_review_comment(
21705 &mut self,
21706 comment_id: usize,
21707 _window: &mut Window,
21708 cx: &mut Context<Self>,
21709 ) {
21710 // Find which hunk this comment belongs to
21711 let hunk_key = self
21712 .stored_review_comments
21713 .iter()
21714 .find_map(|(key, comments)| {
21715 if comments.iter().any(|c| c.id == comment_id) {
21716 Some(key.clone())
21717 } else {
21718 None
21719 }
21720 });
21721
21722 // Remove the inline editor and its subscription
21723 if let Some(hunk_key) = hunk_key {
21724 let snapshot = self.buffer.read(cx).snapshot(cx);
21725 if let Some(overlay) = self
21726 .diff_review_overlays
21727 .iter_mut()
21728 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21729 {
21730 overlay.inline_edit_editors.remove(&comment_id);
21731 overlay.inline_edit_subscriptions.remove(&comment_id);
21732 }
21733 }
21734
21735 // Clear editing state
21736 self.set_comment_editing(comment_id, false, cx);
21737 }
21738
21739 /// Action handler for ConfirmEditReviewComment.
21740 pub fn confirm_edit_review_comment_action(
21741 &mut self,
21742 action: &ConfirmEditReviewComment,
21743 window: &mut Window,
21744 cx: &mut Context<Self>,
21745 ) {
21746 self.confirm_edit_review_comment(action.id, window, cx);
21747 }
21748
21749 /// Action handler for CancelEditReviewComment.
21750 pub fn cancel_edit_review_comment_action(
21751 &mut self,
21752 action: &CancelEditReviewComment,
21753 window: &mut Window,
21754 cx: &mut Context<Self>,
21755 ) {
21756 self.cancel_edit_review_comment(action.id, window, cx);
21757 }
21758
21759 /// Handles the DeleteReviewComment action - removes a comment.
21760 pub fn delete_review_comment(
21761 &mut self,
21762 action: &DeleteReviewComment,
21763 window: &mut Window,
21764 cx: &mut Context<Self>,
21765 ) {
21766 // Get the hunk key before removing the comment
21767 // Find the hunk key from the comment itself
21768 let comment_id = action.id;
21769 let hunk_key = self
21770 .stored_review_comments
21771 .iter()
21772 .find_map(|(key, comments)| {
21773 if comments.iter().any(|c| c.id == comment_id) {
21774 Some(key.clone())
21775 } else {
21776 None
21777 }
21778 });
21779
21780 // Also get it from the overlay for refresh purposes
21781 let overlay_hunk_key = self
21782 .diff_review_overlays
21783 .first()
21784 .map(|o| o.hunk_key.clone());
21785
21786 self.remove_review_comment(action.id, cx);
21787
21788 // Refresh the overlay height after removing a comment
21789 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
21790 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21791 }
21792 }
21793
21794 fn render_diff_review_overlay(
21795 prompt_editor: &Entity<Editor>,
21796 hunk_key: &DiffHunkKey,
21797 editor_handle: &WeakEntity<Editor>,
21798 cx: &mut BlockContext,
21799 ) -> AnyElement {
21800 let theme = cx.theme();
21801 let colors = theme.colors();
21802
21803 // Get stored comments, expanded state, inline editors, and user avatar from the editor
21804 let (comments, comments_expanded, inline_editors, user_avatar_uri) = editor_handle
21805 .upgrade()
21806 .map(|editor| {
21807 let editor = editor.read(cx);
21808 let snapshot = editor.buffer().read(cx).snapshot(cx);
21809 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
21810 let snapshot = editor.buffer.read(cx).snapshot(cx);
21811 let (expanded, editors, avatar_uri) = editor
21812 .diff_review_overlays
21813 .iter()
21814 .find(|overlay| Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21815 .as_ref()
21816 .map(|o| {
21817 (
21818 o.comments_expanded,
21819 o.inline_edit_editors.clone(),
21820 o.user_avatar_uri.clone(),
21821 )
21822 })
21823 .unwrap_or((true, HashMap::default(), None));
21824 (comments, expanded, editors, avatar_uri)
21825 })
21826 .unwrap_or((Vec::new(), true, HashMap::default(), None));
21827
21828 let comment_count = comments.len();
21829 let avatar_size = px(20.);
21830 let action_icon_size = IconSize::XSmall;
21831
21832 v_flex()
21833 .w_full()
21834 .bg(colors.editor_background)
21835 .border_b_1()
21836 .border_color(colors.border)
21837 .px_2()
21838 .pb_2()
21839 .gap_2()
21840 // Top row: editable input with user's avatar
21841 .child(
21842 h_flex()
21843 .w_full()
21844 .items_center()
21845 .gap_2()
21846 .px_2()
21847 .py_1p5()
21848 .rounded_md()
21849 .bg(colors.surface_background)
21850 .child(
21851 div()
21852 .size(avatar_size)
21853 .flex_shrink_0()
21854 .rounded_full()
21855 .overflow_hidden()
21856 .child(if let Some(ref avatar_uri) = user_avatar_uri {
21857 Avatar::new(avatar_uri.clone())
21858 .size(avatar_size)
21859 .into_any_element()
21860 } else {
21861 Icon::new(IconName::Person)
21862 .size(IconSize::Small)
21863 .color(ui::Color::Muted)
21864 .into_any_element()
21865 }),
21866 )
21867 .child(
21868 div()
21869 .flex_1()
21870 .border_1()
21871 .border_color(colors.border)
21872 .rounded_md()
21873 .bg(colors.editor_background)
21874 .px_2()
21875 .py_1()
21876 .child(prompt_editor.clone()),
21877 )
21878 .child(
21879 h_flex()
21880 .flex_shrink_0()
21881 .gap_1()
21882 .child(
21883 IconButton::new("diff-review-close", IconName::Close)
21884 .icon_color(ui::Color::Muted)
21885 .icon_size(action_icon_size)
21886 .tooltip(Tooltip::text("Close"))
21887 .on_click(|_, window, cx| {
21888 window
21889 .dispatch_action(Box::new(crate::actions::Cancel), cx);
21890 }),
21891 )
21892 .child(
21893 IconButton::new("diff-review-add", IconName::Return)
21894 .icon_color(ui::Color::Muted)
21895 .icon_size(action_icon_size)
21896 .tooltip(Tooltip::text("Add comment"))
21897 .on_click(|_, window, cx| {
21898 window.dispatch_action(
21899 Box::new(crate::actions::SubmitDiffReviewComment),
21900 cx,
21901 );
21902 }),
21903 ),
21904 ),
21905 )
21906 // Expandable comments section (only shown when there are comments)
21907 .when(comment_count > 0, |el| {
21908 el.child(Self::render_comments_section(
21909 comments,
21910 comments_expanded,
21911 inline_editors,
21912 user_avatar_uri,
21913 avatar_size,
21914 action_icon_size,
21915 colors,
21916 ))
21917 })
21918 .into_any_element()
21919 }
21920
21921 fn render_comments_section(
21922 comments: Vec<StoredReviewComment>,
21923 expanded: bool,
21924 inline_editors: HashMap<usize, Entity<Editor>>,
21925 user_avatar_uri: Option<SharedUri>,
21926 avatar_size: Pixels,
21927 action_icon_size: IconSize,
21928 colors: &theme::ThemeColors,
21929 ) -> impl IntoElement {
21930 let comment_count = comments.len();
21931
21932 v_flex()
21933 .w_full()
21934 .gap_1()
21935 // Header with expand/collapse toggle
21936 .child(
21937 h_flex()
21938 .id("review-comments-header")
21939 .w_full()
21940 .items_center()
21941 .gap_1()
21942 .px_2()
21943 .py_1()
21944 .cursor_pointer()
21945 .rounded_md()
21946 .hover(|style| style.bg(colors.ghost_element_hover))
21947 .on_click(|_, window: &mut Window, cx| {
21948 window.dispatch_action(
21949 Box::new(crate::actions::ToggleReviewCommentsExpanded),
21950 cx,
21951 );
21952 })
21953 .child(
21954 Icon::new(if expanded {
21955 IconName::ChevronDown
21956 } else {
21957 IconName::ChevronRight
21958 })
21959 .size(IconSize::Small)
21960 .color(ui::Color::Muted),
21961 )
21962 .child(
21963 Label::new(format!(
21964 "{} Comment{}",
21965 comment_count,
21966 if comment_count == 1 { "" } else { "s" }
21967 ))
21968 .size(LabelSize::Small)
21969 .color(Color::Muted),
21970 ),
21971 )
21972 // Comments list (when expanded)
21973 .when(expanded, |el| {
21974 el.children(comments.into_iter().map(|comment| {
21975 let inline_editor = inline_editors.get(&comment.id).cloned();
21976 Self::render_comment_row(
21977 comment,
21978 inline_editor,
21979 user_avatar_uri.clone(),
21980 avatar_size,
21981 action_icon_size,
21982 colors,
21983 )
21984 }))
21985 })
21986 }
21987
21988 fn render_comment_row(
21989 comment: StoredReviewComment,
21990 inline_editor: Option<Entity<Editor>>,
21991 user_avatar_uri: Option<SharedUri>,
21992 avatar_size: Pixels,
21993 action_icon_size: IconSize,
21994 colors: &theme::ThemeColors,
21995 ) -> impl IntoElement {
21996 let comment_id = comment.id;
21997 let is_editing = inline_editor.is_some();
21998
21999 h_flex()
22000 .w_full()
22001 .items_center()
22002 .gap_2()
22003 .px_2()
22004 .py_1p5()
22005 .rounded_md()
22006 .bg(colors.surface_background)
22007 .child(
22008 div()
22009 .size(avatar_size)
22010 .flex_shrink_0()
22011 .rounded_full()
22012 .overflow_hidden()
22013 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22014 Avatar::new(avatar_uri.clone())
22015 .size(avatar_size)
22016 .into_any_element()
22017 } else {
22018 Icon::new(IconName::Person)
22019 .size(IconSize::Small)
22020 .color(ui::Color::Muted)
22021 .into_any_element()
22022 }),
22023 )
22024 .child(if let Some(editor) = inline_editor {
22025 // Inline edit mode: show an editable text field
22026 div()
22027 .flex_1()
22028 .border_1()
22029 .border_color(colors.border)
22030 .rounded_md()
22031 .bg(colors.editor_background)
22032 .px_2()
22033 .py_1()
22034 .child(editor)
22035 .into_any_element()
22036 } else {
22037 // Display mode: show the comment text
22038 div()
22039 .flex_1()
22040 .text_sm()
22041 .text_color(colors.text)
22042 .child(comment.comment)
22043 .into_any_element()
22044 })
22045 .child(if is_editing {
22046 // Editing mode: show close and confirm buttons
22047 h_flex()
22048 .gap_1()
22049 .child(
22050 IconButton::new(
22051 format!("diff-review-cancel-edit-{comment_id}"),
22052 IconName::Close,
22053 )
22054 .icon_color(ui::Color::Muted)
22055 .icon_size(action_icon_size)
22056 .tooltip(Tooltip::text("Cancel"))
22057 .on_click(move |_, window, cx| {
22058 window.dispatch_action(
22059 Box::new(crate::actions::CancelEditReviewComment {
22060 id: comment_id,
22061 }),
22062 cx,
22063 );
22064 }),
22065 )
22066 .child(
22067 IconButton::new(
22068 format!("diff-review-confirm-edit-{comment_id}"),
22069 IconName::Return,
22070 )
22071 .icon_color(ui::Color::Muted)
22072 .icon_size(action_icon_size)
22073 .tooltip(Tooltip::text("Confirm"))
22074 .on_click(move |_, window, cx| {
22075 window.dispatch_action(
22076 Box::new(crate::actions::ConfirmEditReviewComment {
22077 id: comment_id,
22078 }),
22079 cx,
22080 );
22081 }),
22082 )
22083 .into_any_element()
22084 } else {
22085 // Display mode: no action buttons for now (edit/delete not yet implemented)
22086 gpui::Empty.into_any_element()
22087 })
22088 }
22089
22090 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22091 if self.display_map.read(cx).masked != masked {
22092 self.display_map.update(cx, |map, _| map.masked = masked);
22093 }
22094 cx.notify()
22095 }
22096
22097 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22098 self.show_wrap_guides = Some(show_wrap_guides);
22099 cx.notify();
22100 }
22101
22102 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22103 self.show_indent_guides = Some(show_indent_guides);
22104 cx.notify();
22105 }
22106
22107 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22108 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22109 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22110 && let Some(dir) = file.abs_path(cx).parent()
22111 {
22112 return Some(dir.to_owned());
22113 }
22114 }
22115
22116 None
22117 }
22118
22119 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22120 self.active_excerpt(cx)?
22121 .1
22122 .read(cx)
22123 .file()
22124 .and_then(|f| f.as_local())
22125 }
22126
22127 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22128 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22129 let buffer = buffer.read(cx);
22130 if let Some(project_path) = buffer.project_path(cx) {
22131 let project = self.project()?.read(cx);
22132 project.absolute_path(&project_path, cx)
22133 } else {
22134 buffer
22135 .file()
22136 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22137 }
22138 })
22139 }
22140
22141 pub fn reveal_in_finder(
22142 &mut self,
22143 _: &RevealInFileManager,
22144 _window: &mut Window,
22145 cx: &mut Context<Self>,
22146 ) {
22147 if let Some(target) = self.target_file(cx) {
22148 cx.reveal_path(&target.abs_path(cx));
22149 }
22150 }
22151
22152 pub fn copy_path(
22153 &mut self,
22154 _: &zed_actions::workspace::CopyPath,
22155 _window: &mut Window,
22156 cx: &mut Context<Self>,
22157 ) {
22158 if let Some(path) = self.target_file_abs_path(cx)
22159 && let Some(path) = path.to_str()
22160 {
22161 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22162 } else {
22163 cx.propagate();
22164 }
22165 }
22166
22167 pub fn copy_relative_path(
22168 &mut self,
22169 _: &zed_actions::workspace::CopyRelativePath,
22170 _window: &mut Window,
22171 cx: &mut Context<Self>,
22172 ) {
22173 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22174 let project = self.project()?.read(cx);
22175 let path = buffer.read(cx).file()?.path();
22176 let path = path.display(project.path_style(cx));
22177 Some(path)
22178 }) {
22179 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22180 } else {
22181 cx.propagate();
22182 }
22183 }
22184
22185 /// Returns the project path for the editor's buffer, if any buffer is
22186 /// opened in the editor.
22187 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22188 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22189 buffer.read(cx).project_path(cx)
22190 } else {
22191 None
22192 }
22193 }
22194
22195 // Returns true if the editor handled a go-to-line request
22196 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22197 maybe!({
22198 let breakpoint_store = self.breakpoint_store.as_ref()?;
22199
22200 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22201 else {
22202 self.clear_row_highlights::<ActiveDebugLine>();
22203 return None;
22204 };
22205
22206 let position = active_stack_frame.position;
22207 let buffer_id = position.buffer_id?;
22208 let snapshot = self
22209 .project
22210 .as_ref()?
22211 .read(cx)
22212 .buffer_for_id(buffer_id, cx)?
22213 .read(cx)
22214 .snapshot();
22215
22216 let mut handled = false;
22217 for (id, ExcerptRange { context, .. }) in
22218 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22219 {
22220 if context.start.cmp(&position, &snapshot).is_ge()
22221 || context.end.cmp(&position, &snapshot).is_lt()
22222 {
22223 continue;
22224 }
22225 let snapshot = self.buffer.read(cx).snapshot(cx);
22226 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22227
22228 handled = true;
22229 self.clear_row_highlights::<ActiveDebugLine>();
22230
22231 self.go_to_line::<ActiveDebugLine>(
22232 multibuffer_anchor,
22233 Some(cx.theme().colors().editor_debugger_active_line_background),
22234 window,
22235 cx,
22236 );
22237
22238 cx.notify();
22239 }
22240
22241 handled.then_some(())
22242 })
22243 .is_some()
22244 }
22245
22246 pub fn copy_file_name_without_extension(
22247 &mut self,
22248 _: &CopyFileNameWithoutExtension,
22249 _: &mut Window,
22250 cx: &mut Context<Self>,
22251 ) {
22252 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22253 let file = buffer.read(cx).file()?;
22254 file.path().file_stem()
22255 }) {
22256 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22257 }
22258 }
22259
22260 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22261 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22262 let file = buffer.read(cx).file()?;
22263 Some(file.file_name(cx))
22264 }) {
22265 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22266 }
22267 }
22268
22269 pub fn toggle_git_blame(
22270 &mut self,
22271 _: &::git::Blame,
22272 window: &mut Window,
22273 cx: &mut Context<Self>,
22274 ) {
22275 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22276
22277 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22278 self.start_git_blame(true, window, cx);
22279 }
22280
22281 cx.notify();
22282 }
22283
22284 pub fn toggle_git_blame_inline(
22285 &mut self,
22286 _: &ToggleGitBlameInline,
22287 window: &mut Window,
22288 cx: &mut Context<Self>,
22289 ) {
22290 self.toggle_git_blame_inline_internal(true, window, cx);
22291 cx.notify();
22292 }
22293
22294 pub fn open_git_blame_commit(
22295 &mut self,
22296 _: &OpenGitBlameCommit,
22297 window: &mut Window,
22298 cx: &mut Context<Self>,
22299 ) {
22300 self.open_git_blame_commit_internal(window, cx);
22301 }
22302
22303 fn open_git_blame_commit_internal(
22304 &mut self,
22305 window: &mut Window,
22306 cx: &mut Context<Self>,
22307 ) -> Option<()> {
22308 let blame = self.blame.as_ref()?;
22309 let snapshot = self.snapshot(window, cx);
22310 let cursor = self
22311 .selections
22312 .newest::<Point>(&snapshot.display_snapshot)
22313 .head();
22314 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22315 let (_, blame_entry) = blame
22316 .update(cx, |blame, cx| {
22317 blame
22318 .blame_for_rows(
22319 &[RowInfo {
22320 buffer_id: Some(buffer.remote_id()),
22321 buffer_row: Some(point.row),
22322 ..Default::default()
22323 }],
22324 cx,
22325 )
22326 .next()
22327 })
22328 .flatten()?;
22329 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22330 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22331 let workspace = self.workspace()?.downgrade();
22332 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22333 None
22334 }
22335
22336 pub fn git_blame_inline_enabled(&self) -> bool {
22337 self.git_blame_inline_enabled
22338 }
22339
22340 pub fn toggle_selection_menu(
22341 &mut self,
22342 _: &ToggleSelectionMenu,
22343 _: &mut Window,
22344 cx: &mut Context<Self>,
22345 ) {
22346 self.show_selection_menu = self
22347 .show_selection_menu
22348 .map(|show_selections_menu| !show_selections_menu)
22349 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22350
22351 cx.notify();
22352 }
22353
22354 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22355 self.show_selection_menu
22356 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22357 }
22358
22359 fn start_git_blame(
22360 &mut self,
22361 user_triggered: bool,
22362 window: &mut Window,
22363 cx: &mut Context<Self>,
22364 ) {
22365 if let Some(project) = self.project() {
22366 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22367 && buffer.read(cx).file().is_none()
22368 {
22369 return;
22370 }
22371
22372 let focused = self.focus_handle(cx).contains_focused(window, cx);
22373
22374 let project = project.clone();
22375 let blame = cx
22376 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22377 self.blame_subscription =
22378 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22379 self.blame = Some(blame);
22380 }
22381 }
22382
22383 fn toggle_git_blame_inline_internal(
22384 &mut self,
22385 user_triggered: bool,
22386 window: &mut Window,
22387 cx: &mut Context<Self>,
22388 ) {
22389 if self.git_blame_inline_enabled {
22390 self.git_blame_inline_enabled = false;
22391 self.show_git_blame_inline = false;
22392 self.show_git_blame_inline_delay_task.take();
22393 } else {
22394 self.git_blame_inline_enabled = true;
22395 self.start_git_blame_inline(user_triggered, window, cx);
22396 }
22397
22398 cx.notify();
22399 }
22400
22401 fn start_git_blame_inline(
22402 &mut self,
22403 user_triggered: bool,
22404 window: &mut Window,
22405 cx: &mut Context<Self>,
22406 ) {
22407 self.start_git_blame(user_triggered, window, cx);
22408
22409 if ProjectSettings::get_global(cx)
22410 .git
22411 .inline_blame_delay()
22412 .is_some()
22413 {
22414 self.start_inline_blame_timer(window, cx);
22415 } else {
22416 self.show_git_blame_inline = true
22417 }
22418 }
22419
22420 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22421 self.blame.as_ref()
22422 }
22423
22424 pub fn show_git_blame_gutter(&self) -> bool {
22425 self.show_git_blame_gutter
22426 }
22427
22428 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22429 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22430 }
22431
22432 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22433 self.show_git_blame_inline
22434 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22435 && !self.newest_selection_head_on_empty_line(cx)
22436 && self.has_blame_entries(cx)
22437 }
22438
22439 fn has_blame_entries(&self, cx: &App) -> bool {
22440 self.blame()
22441 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22442 }
22443
22444 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22445 let cursor_anchor = self.selections.newest_anchor().head();
22446
22447 let snapshot = self.buffer.read(cx).snapshot(cx);
22448 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22449
22450 snapshot.line_len(buffer_row) == 0
22451 }
22452
22453 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22454 let buffer_and_selection = maybe!({
22455 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22456 let selection_range = selection.range();
22457
22458 let multi_buffer = self.buffer().read(cx);
22459 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22460 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
22461
22462 let (buffer, range, _) = if selection.reversed {
22463 buffer_ranges.first()
22464 } else {
22465 buffer_ranges.last()
22466 }?;
22467
22468 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
22469 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
22470
22471 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22472 let selection = start_row_in_buffer..end_row_in_buffer;
22473
22474 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
22475 };
22476
22477 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22478
22479 Some((
22480 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22481 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, Bias::Left, buffer)
22482 ..buffer_diff_snapshot.row_to_base_text_row(
22483 end_row_in_buffer,
22484 Bias::Left,
22485 buffer,
22486 ),
22487 ))
22488 });
22489
22490 let Some((buffer, selection)) = buffer_and_selection else {
22491 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22492 };
22493
22494 let Some(project) = self.project() else {
22495 return Task::ready(Err(anyhow!("editor does not have project")));
22496 };
22497
22498 project.update(cx, |project, cx| {
22499 project.get_permalink_to_line(&buffer, selection, cx)
22500 })
22501 }
22502
22503 pub fn copy_permalink_to_line(
22504 &mut self,
22505 _: &CopyPermalinkToLine,
22506 window: &mut Window,
22507 cx: &mut Context<Self>,
22508 ) {
22509 let permalink_task = self.get_permalink_to_line(cx);
22510 let workspace = self.workspace();
22511
22512 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22513 Ok(permalink) => {
22514 cx.update(|_, cx| {
22515 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22516 })
22517 .ok();
22518 }
22519 Err(err) => {
22520 let message = format!("Failed to copy permalink: {err}");
22521
22522 anyhow::Result::<()>::Err(err).log_err();
22523
22524 if let Some(workspace) = workspace {
22525 workspace
22526 .update_in(cx, |workspace, _, cx| {
22527 struct CopyPermalinkToLine;
22528
22529 workspace.show_toast(
22530 Toast::new(
22531 NotificationId::unique::<CopyPermalinkToLine>(),
22532 message,
22533 ),
22534 cx,
22535 )
22536 })
22537 .ok();
22538 }
22539 }
22540 })
22541 .detach();
22542 }
22543
22544 pub fn copy_file_location(
22545 &mut self,
22546 _: &CopyFileLocation,
22547 _: &mut Window,
22548 cx: &mut Context<Self>,
22549 ) {
22550 let selection = self
22551 .selections
22552 .newest::<Point>(&self.display_snapshot(cx))
22553 .start
22554 .row
22555 + 1;
22556 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22557 let project = self.project()?.read(cx);
22558 let file = buffer.read(cx).file()?;
22559 let path = file.path().display(project.path_style(cx));
22560
22561 Some(format!("{path}:{selection}"))
22562 }) {
22563 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
22564 }
22565 }
22566
22567 pub fn open_permalink_to_line(
22568 &mut self,
22569 _: &OpenPermalinkToLine,
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.open_url(permalink.as_ref());
22580 })
22581 .ok();
22582 }
22583 Err(err) => {
22584 let message = format!("Failed to open permalink: {err}");
22585
22586 anyhow::Result::<()>::Err(err).log_err();
22587
22588 if let Some(workspace) = workspace {
22589 workspace.update(cx, |workspace, cx| {
22590 struct OpenPermalinkToLine;
22591
22592 workspace.show_toast(
22593 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
22594 cx,
22595 )
22596 });
22597 }
22598 }
22599 })
22600 .detach();
22601 }
22602
22603 pub fn insert_uuid_v4(
22604 &mut self,
22605 _: &InsertUuidV4,
22606 window: &mut Window,
22607 cx: &mut Context<Self>,
22608 ) {
22609 self.insert_uuid(UuidVersion::V4, window, cx);
22610 }
22611
22612 pub fn insert_uuid_v7(
22613 &mut self,
22614 _: &InsertUuidV7,
22615 window: &mut Window,
22616 cx: &mut Context<Self>,
22617 ) {
22618 self.insert_uuid(UuidVersion::V7, window, cx);
22619 }
22620
22621 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
22622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
22623 self.transact(window, cx, |this, window, cx| {
22624 let edits = this
22625 .selections
22626 .all::<Point>(&this.display_snapshot(cx))
22627 .into_iter()
22628 .map(|selection| {
22629 let uuid = match version {
22630 UuidVersion::V4 => uuid::Uuid::new_v4(),
22631 UuidVersion::V7 => uuid::Uuid::now_v7(),
22632 };
22633
22634 (selection.range(), uuid.to_string())
22635 });
22636 this.edit(edits, cx);
22637 this.refresh_edit_prediction(true, false, window, cx);
22638 });
22639 }
22640
22641 pub fn open_selections_in_multibuffer(
22642 &mut self,
22643 _: &OpenSelectionsInMultibuffer,
22644 window: &mut Window,
22645 cx: &mut Context<Self>,
22646 ) {
22647 let multibuffer = self.buffer.read(cx);
22648
22649 let Some(buffer) = multibuffer.as_singleton() else {
22650 return;
22651 };
22652
22653 let Some(workspace) = self.workspace() else {
22654 return;
22655 };
22656
22657 let title = multibuffer.title(cx).to_string();
22658
22659 let locations = self
22660 .selections
22661 .all_anchors(&self.display_snapshot(cx))
22662 .iter()
22663 .map(|selection| {
22664 (
22665 buffer.clone(),
22666 (selection.start.text_anchor..selection.end.text_anchor)
22667 .to_point(buffer.read(cx)),
22668 )
22669 })
22670 .into_group_map();
22671
22672 cx.spawn_in(window, async move |_, cx| {
22673 workspace.update_in(cx, |workspace, window, cx| {
22674 Self::open_locations_in_multibuffer(
22675 workspace,
22676 locations,
22677 format!("Selections for '{title}'"),
22678 false,
22679 false,
22680 MultibufferSelectionMode::All,
22681 window,
22682 cx,
22683 );
22684 })
22685 })
22686 .detach();
22687 }
22688
22689 /// Adds a row highlight for the given range. If a row has multiple highlights, the
22690 /// last highlight added will be used.
22691 ///
22692 /// If the range ends at the beginning of a line, then that line will not be highlighted.
22693 pub fn highlight_rows<T: 'static>(
22694 &mut self,
22695 range: Range<Anchor>,
22696 color: Hsla,
22697 options: RowHighlightOptions,
22698 cx: &mut Context<Self>,
22699 ) {
22700 let snapshot = self.buffer().read(cx).snapshot(cx);
22701 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
22702 let ix = row_highlights.binary_search_by(|highlight| {
22703 Ordering::Equal
22704 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
22705 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
22706 });
22707
22708 if let Err(mut ix) = ix {
22709 let index = post_inc(&mut self.highlight_order);
22710
22711 // If this range intersects with the preceding highlight, then merge it with
22712 // the preceding highlight. Otherwise insert a new highlight.
22713 let mut merged = false;
22714 if ix > 0 {
22715 let prev_highlight = &mut row_highlights[ix - 1];
22716 if prev_highlight
22717 .range
22718 .end
22719 .cmp(&range.start, &snapshot)
22720 .is_ge()
22721 {
22722 ix -= 1;
22723 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
22724 prev_highlight.range.end = range.end;
22725 }
22726 merged = true;
22727 prev_highlight.index = index;
22728 prev_highlight.color = color;
22729 prev_highlight.options = options;
22730 }
22731 }
22732
22733 if !merged {
22734 row_highlights.insert(
22735 ix,
22736 RowHighlight {
22737 range,
22738 index,
22739 color,
22740 options,
22741 type_id: TypeId::of::<T>(),
22742 },
22743 );
22744 }
22745
22746 // If any of the following highlights intersect with this one, merge them.
22747 while let Some(next_highlight) = row_highlights.get(ix + 1) {
22748 let highlight = &row_highlights[ix];
22749 if next_highlight
22750 .range
22751 .start
22752 .cmp(&highlight.range.end, &snapshot)
22753 .is_le()
22754 {
22755 if next_highlight
22756 .range
22757 .end
22758 .cmp(&highlight.range.end, &snapshot)
22759 .is_gt()
22760 {
22761 row_highlights[ix].range.end = next_highlight.range.end;
22762 }
22763 row_highlights.remove(ix + 1);
22764 } else {
22765 break;
22766 }
22767 }
22768 }
22769 }
22770
22771 /// Remove any highlighted row ranges of the given type that intersect the
22772 /// given ranges.
22773 pub fn remove_highlighted_rows<T: 'static>(
22774 &mut self,
22775 ranges_to_remove: Vec<Range<Anchor>>,
22776 cx: &mut Context<Self>,
22777 ) {
22778 let snapshot = self.buffer().read(cx).snapshot(cx);
22779 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
22780 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
22781 row_highlights.retain(|highlight| {
22782 while let Some(range_to_remove) = ranges_to_remove.peek() {
22783 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
22784 Ordering::Less | Ordering::Equal => {
22785 ranges_to_remove.next();
22786 }
22787 Ordering::Greater => {
22788 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
22789 Ordering::Less | Ordering::Equal => {
22790 return false;
22791 }
22792 Ordering::Greater => break,
22793 }
22794 }
22795 }
22796 }
22797
22798 true
22799 })
22800 }
22801
22802 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
22803 pub fn clear_row_highlights<T: 'static>(&mut self) {
22804 self.highlighted_rows.remove(&TypeId::of::<T>());
22805 }
22806
22807 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
22808 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
22809 self.highlighted_rows
22810 .get(&TypeId::of::<T>())
22811 .map_or(&[] as &[_], |vec| vec.as_slice())
22812 .iter()
22813 .map(|highlight| (highlight.range.clone(), highlight.color))
22814 }
22815
22816 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
22817 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
22818 /// Allows to ignore certain kinds of highlights.
22819 pub fn highlighted_display_rows(
22820 &self,
22821 window: &mut Window,
22822 cx: &mut App,
22823 ) -> BTreeMap<DisplayRow, LineHighlight> {
22824 let snapshot = self.snapshot(window, cx);
22825 let mut used_highlight_orders = HashMap::default();
22826 self.highlighted_rows
22827 .iter()
22828 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
22829 .fold(
22830 BTreeMap::<DisplayRow, LineHighlight>::new(),
22831 |mut unique_rows, highlight| {
22832 let start = highlight.range.start.to_display_point(&snapshot);
22833 let end = highlight.range.end.to_display_point(&snapshot);
22834 let start_row = start.row().0;
22835 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
22836 {
22837 end.row().0.saturating_sub(1)
22838 } else {
22839 end.row().0
22840 };
22841 for row in start_row..=end_row {
22842 let used_index =
22843 used_highlight_orders.entry(row).or_insert(highlight.index);
22844 if highlight.index >= *used_index {
22845 *used_index = highlight.index;
22846 unique_rows.insert(
22847 DisplayRow(row),
22848 LineHighlight {
22849 include_gutter: highlight.options.include_gutter,
22850 border: None,
22851 background: highlight.color.into(),
22852 type_id: Some(highlight.type_id),
22853 },
22854 );
22855 }
22856 }
22857 unique_rows
22858 },
22859 )
22860 }
22861
22862 pub fn highlighted_display_row_for_autoscroll(
22863 &self,
22864 snapshot: &DisplaySnapshot,
22865 ) -> Option<DisplayRow> {
22866 self.highlighted_rows
22867 .values()
22868 .flat_map(|highlighted_rows| highlighted_rows.iter())
22869 .filter_map(|highlight| {
22870 if highlight.options.autoscroll {
22871 Some(highlight.range.start.to_display_point(snapshot).row())
22872 } else {
22873 None
22874 }
22875 })
22876 .min()
22877 }
22878
22879 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
22880 self.highlight_background::<SearchWithinRange>(
22881 ranges,
22882 |_, colors| colors.colors().editor_document_highlight_read_background,
22883 cx,
22884 )
22885 }
22886
22887 pub fn set_breadcrumb_header(&mut self, new_header: String) {
22888 self.breadcrumb_header = Some(new_header);
22889 }
22890
22891 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
22892 self.clear_background_highlights::<SearchWithinRange>(cx);
22893 }
22894
22895 pub fn highlight_background<T: 'static>(
22896 &mut self,
22897 ranges: &[Range<Anchor>],
22898 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
22899 cx: &mut Context<Self>,
22900 ) {
22901 self.background_highlights.insert(
22902 HighlightKey::Type(TypeId::of::<T>()),
22903 (Arc::new(color_fetcher), Arc::from(ranges)),
22904 );
22905 self.scrollbar_marker_state.dirty = true;
22906 cx.notify();
22907 }
22908
22909 pub fn highlight_background_key<T: 'static>(
22910 &mut self,
22911 key: usize,
22912 ranges: &[Range<Anchor>],
22913 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
22914 cx: &mut Context<Self>,
22915 ) {
22916 self.background_highlights.insert(
22917 HighlightKey::TypePlus(TypeId::of::<T>(), key),
22918 (Arc::new(color_fetcher), Arc::from(ranges)),
22919 );
22920 self.scrollbar_marker_state.dirty = true;
22921 cx.notify();
22922 }
22923
22924 pub fn clear_background_highlights<T: 'static>(
22925 &mut self,
22926 cx: &mut Context<Self>,
22927 ) -> Option<BackgroundHighlight> {
22928 let text_highlights = self
22929 .background_highlights
22930 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
22931 if !text_highlights.1.is_empty() {
22932 self.scrollbar_marker_state.dirty = true;
22933 cx.notify();
22934 }
22935 Some(text_highlights)
22936 }
22937
22938 pub fn highlight_gutter<T: 'static>(
22939 &mut self,
22940 ranges: impl Into<Vec<Range<Anchor>>>,
22941 color_fetcher: fn(&App) -> Hsla,
22942 cx: &mut Context<Self>,
22943 ) {
22944 self.gutter_highlights
22945 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
22946 cx.notify();
22947 }
22948
22949 pub fn clear_gutter_highlights<T: 'static>(
22950 &mut self,
22951 cx: &mut Context<Self>,
22952 ) -> Option<GutterHighlight> {
22953 cx.notify();
22954 self.gutter_highlights.remove(&TypeId::of::<T>())
22955 }
22956
22957 pub fn insert_gutter_highlight<T: 'static>(
22958 &mut self,
22959 range: Range<Anchor>,
22960 color_fetcher: fn(&App) -> Hsla,
22961 cx: &mut Context<Self>,
22962 ) {
22963 let snapshot = self.buffer().read(cx).snapshot(cx);
22964 let mut highlights = self
22965 .gutter_highlights
22966 .remove(&TypeId::of::<T>())
22967 .map(|(_, highlights)| highlights)
22968 .unwrap_or_default();
22969 let ix = highlights.binary_search_by(|highlight| {
22970 Ordering::Equal
22971 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
22972 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
22973 });
22974 if let Err(ix) = ix {
22975 highlights.insert(ix, range);
22976 }
22977 self.gutter_highlights
22978 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
22979 }
22980
22981 pub fn remove_gutter_highlights<T: 'static>(
22982 &mut self,
22983 ranges_to_remove: Vec<Range<Anchor>>,
22984 cx: &mut Context<Self>,
22985 ) {
22986 let snapshot = self.buffer().read(cx).snapshot(cx);
22987 let Some((color_fetcher, mut gutter_highlights)) =
22988 self.gutter_highlights.remove(&TypeId::of::<T>())
22989 else {
22990 return;
22991 };
22992 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
22993 gutter_highlights.retain(|highlight| {
22994 while let Some(range_to_remove) = ranges_to_remove.peek() {
22995 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
22996 Ordering::Less | Ordering::Equal => {
22997 ranges_to_remove.next();
22998 }
22999 Ordering::Greater => {
23000 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23001 Ordering::Less | Ordering::Equal => {
23002 return false;
23003 }
23004 Ordering::Greater => break,
23005 }
23006 }
23007 }
23008 }
23009
23010 true
23011 });
23012 self.gutter_highlights
23013 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23014 }
23015
23016 #[cfg(feature = "test-support")]
23017 pub fn all_text_highlights(
23018 &self,
23019 window: &mut Window,
23020 cx: &mut Context<Self>,
23021 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23022 let snapshot = self.snapshot(window, cx);
23023 self.display_map.update(cx, |display_map, _| {
23024 display_map
23025 .all_text_highlights()
23026 .map(|highlight| {
23027 let (style, ranges) = highlight.as_ref();
23028 (
23029 *style,
23030 ranges
23031 .iter()
23032 .map(|range| range.clone().to_display_points(&snapshot))
23033 .collect(),
23034 )
23035 })
23036 .collect()
23037 })
23038 }
23039
23040 #[cfg(feature = "test-support")]
23041 pub fn all_text_background_highlights(
23042 &self,
23043 window: &mut Window,
23044 cx: &mut Context<Self>,
23045 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23046 let snapshot = self.snapshot(window, cx);
23047 let buffer = &snapshot.buffer_snapshot();
23048 let start = buffer.anchor_before(MultiBufferOffset(0));
23049 let end = buffer.anchor_after(buffer.len());
23050 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23051 }
23052
23053 #[cfg(any(test, feature = "test-support"))]
23054 pub fn sorted_background_highlights_in_range(
23055 &self,
23056 search_range: Range<Anchor>,
23057 display_snapshot: &DisplaySnapshot,
23058 theme: &Theme,
23059 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23060 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23061 res.sort_by(|a, b| {
23062 a.0.start
23063 .cmp(&b.0.start)
23064 .then_with(|| a.0.end.cmp(&b.0.end))
23065 .then_with(|| a.1.cmp(&b.1))
23066 });
23067 res
23068 }
23069
23070 #[cfg(feature = "test-support")]
23071 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23072 let snapshot = self.buffer().read(cx).snapshot(cx);
23073
23074 let highlights = self
23075 .background_highlights
23076 .get(&HighlightKey::Type(TypeId::of::<
23077 items::BufferSearchHighlights,
23078 >()));
23079
23080 if let Some((_color, ranges)) = highlights {
23081 ranges
23082 .iter()
23083 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23084 .collect_vec()
23085 } else {
23086 vec![]
23087 }
23088 }
23089
23090 fn document_highlights_for_position<'a>(
23091 &'a self,
23092 position: Anchor,
23093 buffer: &'a MultiBufferSnapshot,
23094 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23095 let read_highlights = self
23096 .background_highlights
23097 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
23098 .map(|h| &h.1);
23099 let write_highlights = self
23100 .background_highlights
23101 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
23102 .map(|h| &h.1);
23103 let left_position = position.bias_left(buffer);
23104 let right_position = position.bias_right(buffer);
23105 read_highlights
23106 .into_iter()
23107 .chain(write_highlights)
23108 .flat_map(move |ranges| {
23109 let start_ix = match ranges.binary_search_by(|probe| {
23110 let cmp = probe.end.cmp(&left_position, buffer);
23111 if cmp.is_ge() {
23112 Ordering::Greater
23113 } else {
23114 Ordering::Less
23115 }
23116 }) {
23117 Ok(i) | Err(i) => i,
23118 };
23119
23120 ranges[start_ix..]
23121 .iter()
23122 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23123 })
23124 }
23125
23126 pub fn has_background_highlights<T: 'static>(&self) -> bool {
23127 self.background_highlights
23128 .get(&HighlightKey::Type(TypeId::of::<T>()))
23129 .is_some_and(|(_, highlights)| !highlights.is_empty())
23130 }
23131
23132 /// Returns all background highlights for a given range.
23133 ///
23134 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23135 pub fn background_highlights_in_range(
23136 &self,
23137 search_range: Range<Anchor>,
23138 display_snapshot: &DisplaySnapshot,
23139 theme: &Theme,
23140 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23141 let mut results = Vec::new();
23142 for (color_fetcher, ranges) in self.background_highlights.values() {
23143 let start_ix = match ranges.binary_search_by(|probe| {
23144 let cmp = probe
23145 .end
23146 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23147 if cmp.is_gt() {
23148 Ordering::Greater
23149 } else {
23150 Ordering::Less
23151 }
23152 }) {
23153 Ok(i) | Err(i) => i,
23154 };
23155 for (index, range) in ranges[start_ix..].iter().enumerate() {
23156 if range
23157 .start
23158 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23159 .is_ge()
23160 {
23161 break;
23162 }
23163
23164 let color = color_fetcher(&(start_ix + index), theme);
23165 let start = range.start.to_display_point(display_snapshot);
23166 let end = range.end.to_display_point(display_snapshot);
23167 results.push((start..end, color))
23168 }
23169 }
23170 results
23171 }
23172
23173 pub fn gutter_highlights_in_range(
23174 &self,
23175 search_range: Range<Anchor>,
23176 display_snapshot: &DisplaySnapshot,
23177 cx: &App,
23178 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23179 let mut results = Vec::new();
23180 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23181 let color = color_fetcher(cx);
23182 let start_ix = match ranges.binary_search_by(|probe| {
23183 let cmp = probe
23184 .end
23185 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23186 if cmp.is_gt() {
23187 Ordering::Greater
23188 } else {
23189 Ordering::Less
23190 }
23191 }) {
23192 Ok(i) | Err(i) => i,
23193 };
23194 for range in &ranges[start_ix..] {
23195 if range
23196 .start
23197 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23198 .is_ge()
23199 {
23200 break;
23201 }
23202
23203 let start = range.start.to_display_point(display_snapshot);
23204 let end = range.end.to_display_point(display_snapshot);
23205 results.push((start..end, color))
23206 }
23207 }
23208 results
23209 }
23210
23211 /// Get the text ranges corresponding to the redaction query
23212 pub fn redacted_ranges(
23213 &self,
23214 search_range: Range<Anchor>,
23215 display_snapshot: &DisplaySnapshot,
23216 cx: &App,
23217 ) -> Vec<Range<DisplayPoint>> {
23218 display_snapshot
23219 .buffer_snapshot()
23220 .redacted_ranges(search_range, |file| {
23221 if let Some(file) = file {
23222 file.is_private()
23223 && EditorSettings::get(
23224 Some(SettingsLocation {
23225 worktree_id: file.worktree_id(cx),
23226 path: file.path().as_ref(),
23227 }),
23228 cx,
23229 )
23230 .redact_private_values
23231 } else {
23232 false
23233 }
23234 })
23235 .map(|range| {
23236 range.start.to_display_point(display_snapshot)
23237 ..range.end.to_display_point(display_snapshot)
23238 })
23239 .collect()
23240 }
23241
23242 pub fn highlight_text_key<T: 'static>(
23243 &mut self,
23244 key: usize,
23245 ranges: Vec<Range<Anchor>>,
23246 style: HighlightStyle,
23247 merge: bool,
23248 cx: &mut Context<Self>,
23249 ) {
23250 self.display_map.update(cx, |map, cx| {
23251 map.highlight_text(
23252 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23253 ranges,
23254 style,
23255 merge,
23256 cx,
23257 );
23258 });
23259 cx.notify();
23260 }
23261
23262 pub fn highlight_text<T: 'static>(
23263 &mut self,
23264 ranges: Vec<Range<Anchor>>,
23265 style: HighlightStyle,
23266 cx: &mut Context<Self>,
23267 ) {
23268 self.display_map.update(cx, |map, cx| {
23269 map.highlight_text(
23270 HighlightKey::Type(TypeId::of::<T>()),
23271 ranges,
23272 style,
23273 false,
23274 cx,
23275 )
23276 });
23277 cx.notify();
23278 }
23279
23280 pub fn text_highlights<'a, T: 'static>(
23281 &'a self,
23282 cx: &'a App,
23283 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23284 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
23285 }
23286
23287 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
23288 let cleared = self
23289 .display_map
23290 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
23291 if cleared {
23292 cx.notify();
23293 }
23294 }
23295
23296 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23297 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23298 && self.focus_handle.is_focused(window)
23299 }
23300
23301 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23302 self.show_cursor_when_unfocused = is_enabled;
23303 cx.notify();
23304 }
23305
23306 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23307 cx.notify();
23308 }
23309
23310 fn on_debug_session_event(
23311 &mut self,
23312 _session: Entity<Session>,
23313 event: &SessionEvent,
23314 cx: &mut Context<Self>,
23315 ) {
23316 if let SessionEvent::InvalidateInlineValue = event {
23317 self.refresh_inline_values(cx);
23318 }
23319 }
23320
23321 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23322 let Some(project) = self.project.clone() else {
23323 return;
23324 };
23325
23326 if !self.inline_value_cache.enabled {
23327 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23328 self.splice_inlays(&inlays, Vec::new(), cx);
23329 return;
23330 }
23331
23332 let current_execution_position = self
23333 .highlighted_rows
23334 .get(&TypeId::of::<ActiveDebugLine>())
23335 .and_then(|lines| lines.last().map(|line| line.range.end));
23336
23337 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23338 let inline_values = editor
23339 .update(cx, |editor, cx| {
23340 let Some(current_execution_position) = current_execution_position else {
23341 return Some(Task::ready(Ok(Vec::new())));
23342 };
23343
23344 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23345 let snapshot = buffer.snapshot(cx);
23346
23347 let excerpt = snapshot.excerpt_containing(
23348 current_execution_position..current_execution_position,
23349 )?;
23350
23351 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23352 })?;
23353
23354 let range =
23355 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23356
23357 project.inline_values(buffer, range, cx)
23358 })
23359 .ok()
23360 .flatten()?
23361 .await
23362 .context("refreshing debugger inlays")
23363 .log_err()?;
23364
23365 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23366
23367 for (buffer_id, inline_value) in inline_values
23368 .into_iter()
23369 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23370 {
23371 buffer_inline_values
23372 .entry(buffer_id)
23373 .or_default()
23374 .push(inline_value);
23375 }
23376
23377 editor
23378 .update(cx, |editor, cx| {
23379 let snapshot = editor.buffer.read(cx).snapshot(cx);
23380 let mut new_inlays = Vec::default();
23381
23382 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23383 let buffer_id = buffer_snapshot.remote_id();
23384 buffer_inline_values
23385 .get(&buffer_id)
23386 .into_iter()
23387 .flatten()
23388 .for_each(|hint| {
23389 let inlay = Inlay::debugger(
23390 post_inc(&mut editor.next_inlay_id),
23391 Anchor::in_buffer(excerpt_id, hint.position),
23392 hint.text(),
23393 );
23394 if !inlay.text().chars().contains(&'\n') {
23395 new_inlays.push(inlay);
23396 }
23397 });
23398 }
23399
23400 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23401 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23402
23403 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23404 })
23405 .ok()?;
23406 Some(())
23407 });
23408 }
23409
23410 fn on_buffer_event(
23411 &mut self,
23412 multibuffer: &Entity<MultiBuffer>,
23413 event: &multi_buffer::Event,
23414 window: &mut Window,
23415 cx: &mut Context<Self>,
23416 ) {
23417 match event {
23418 multi_buffer::Event::Edited { edited_buffer } => {
23419 self.scrollbar_marker_state.dirty = true;
23420 self.active_indent_guides_state.dirty = true;
23421 self.refresh_active_diagnostics(cx);
23422 self.refresh_code_actions(window, cx);
23423 self.refresh_single_line_folds(window, cx);
23424 self.refresh_matching_bracket_highlights(window, cx);
23425 if self.has_active_edit_prediction() {
23426 self.update_visible_edit_prediction(window, cx);
23427 }
23428
23429 // Clean up orphaned review comments after edits
23430 self.cleanup_orphaned_review_comments(cx);
23431
23432 if let Some(buffer) = edited_buffer {
23433 if buffer.read(cx).file().is_none() {
23434 cx.emit(EditorEvent::TitleChanged);
23435 }
23436
23437 if self.project.is_some() {
23438 let buffer_id = buffer.read(cx).remote_id();
23439 self.register_buffer(buffer_id, cx);
23440 self.update_lsp_data(Some(buffer_id), window, cx);
23441 self.refresh_inlay_hints(
23442 InlayHintRefreshReason::BufferEdited(buffer_id),
23443 cx,
23444 );
23445 }
23446 }
23447
23448 cx.emit(EditorEvent::BufferEdited);
23449 cx.emit(SearchEvent::MatchesInvalidated);
23450
23451 let Some(project) = &self.project else { return };
23452 let (telemetry, is_via_ssh) = {
23453 let project = project.read(cx);
23454 let telemetry = project.client().telemetry().clone();
23455 let is_via_ssh = project.is_via_remote_server();
23456 (telemetry, is_via_ssh)
23457 };
23458 telemetry.log_edit_event("editor", is_via_ssh);
23459 }
23460 multi_buffer::Event::ExcerptsAdded {
23461 buffer,
23462 predecessor,
23463 excerpts,
23464 } => {
23465 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23466 let buffer_id = buffer.read(cx).remote_id();
23467 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23468 && let Some(project) = &self.project
23469 {
23470 update_uncommitted_diff_for_buffer(
23471 cx.entity(),
23472 project,
23473 [buffer.clone()],
23474 self.buffer.clone(),
23475 cx,
23476 )
23477 .detach();
23478 }
23479 self.update_lsp_data(Some(buffer_id), window, cx);
23480 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23481 self.colorize_brackets(false, cx);
23482 self.refresh_selected_text_highlights(true, window, cx);
23483 cx.emit(EditorEvent::ExcerptsAdded {
23484 buffer: buffer.clone(),
23485 predecessor: *predecessor,
23486 excerpts: excerpts.clone(),
23487 });
23488 }
23489 multi_buffer::Event::ExcerptsRemoved {
23490 ids,
23491 removed_buffer_ids,
23492 } => {
23493 if let Some(inlay_hints) = &mut self.inlay_hints {
23494 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23495 }
23496 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23497 for buffer_id in removed_buffer_ids {
23498 self.registered_buffers.remove(buffer_id);
23499 }
23500 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23501 cx.emit(EditorEvent::ExcerptsRemoved {
23502 ids: ids.clone(),
23503 removed_buffer_ids: removed_buffer_ids.clone(),
23504 });
23505 }
23506 multi_buffer::Event::ExcerptsEdited {
23507 excerpt_ids,
23508 buffer_ids,
23509 } => {
23510 self.display_map.update(cx, |map, cx| {
23511 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23512 });
23513 cx.emit(EditorEvent::ExcerptsEdited {
23514 ids: excerpt_ids.clone(),
23515 });
23516 }
23517 multi_buffer::Event::ExcerptsExpanded { ids } => {
23518 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23519 self.refresh_document_highlights(cx);
23520 for id in ids {
23521 self.fetched_tree_sitter_chunks.remove(id);
23522 }
23523 self.colorize_brackets(false, cx);
23524 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23525 }
23526 multi_buffer::Event::Reparsed(buffer_id) => {
23527 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23528 self.refresh_selected_text_highlights(true, window, cx);
23529 self.colorize_brackets(true, cx);
23530 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23531
23532 cx.emit(EditorEvent::Reparsed(*buffer_id));
23533 }
23534 multi_buffer::Event::DiffHunksToggled => {
23535 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23536 }
23537 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23538 if !is_fresh_language {
23539 self.registered_buffers.remove(&buffer_id);
23540 }
23541 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23542 cx.emit(EditorEvent::Reparsed(*buffer_id));
23543 cx.notify();
23544 }
23545 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
23546 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
23547 multi_buffer::Event::FileHandleChanged
23548 | multi_buffer::Event::Reloaded
23549 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
23550 multi_buffer::Event::DiagnosticsUpdated => {
23551 self.update_diagnostics_state(window, cx);
23552 }
23553 _ => {}
23554 };
23555 }
23556
23557 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
23558 if !self.diagnostics_enabled() {
23559 return;
23560 }
23561 self.refresh_active_diagnostics(cx);
23562 self.refresh_inline_diagnostics(true, window, cx);
23563 self.scrollbar_marker_state.dirty = true;
23564 cx.notify();
23565 }
23566
23567 pub fn start_temporary_diff_override(&mut self) {
23568 self.load_diff_task.take();
23569 self.temporary_diff_override = true;
23570 }
23571
23572 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
23573 self.temporary_diff_override = false;
23574 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
23575 self.buffer.update(cx, |buffer, cx| {
23576 buffer.set_all_diff_hunks_collapsed(cx);
23577 });
23578
23579 if let Some(project) = self.project.clone() {
23580 self.load_diff_task = Some(
23581 update_uncommitted_diff_for_buffer(
23582 cx.entity(),
23583 &project,
23584 self.buffer.read(cx).all_buffers(),
23585 self.buffer.clone(),
23586 cx,
23587 )
23588 .shared(),
23589 );
23590 }
23591 }
23592
23593 fn on_display_map_changed(
23594 &mut self,
23595 _: Entity<DisplayMap>,
23596 _: &mut Window,
23597 cx: &mut Context<Self>,
23598 ) {
23599 cx.notify();
23600 }
23601
23602 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
23603 if !self.mode.is_full() {
23604 return None;
23605 }
23606
23607 let theme_settings = theme::ThemeSettings::get_global(cx);
23608 let theme = cx.theme();
23609 let accent_colors = theme.accents().clone();
23610
23611 let accent_overrides = theme_settings
23612 .theme_overrides
23613 .get(theme.name.as_ref())
23614 .map(|theme_style| &theme_style.accents)
23615 .into_iter()
23616 .flatten()
23617 .chain(
23618 theme_settings
23619 .experimental_theme_overrides
23620 .as_ref()
23621 .map(|overrides| &overrides.accents)
23622 .into_iter()
23623 .flatten(),
23624 )
23625 .flat_map(|accent| accent.0.clone())
23626 .collect();
23627
23628 Some(AccentData {
23629 colors: accent_colors,
23630 overrides: accent_overrides,
23631 })
23632 }
23633
23634 fn fetch_applicable_language_settings(
23635 &self,
23636 cx: &App,
23637 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
23638 if !self.mode.is_full() {
23639 return HashMap::default();
23640 }
23641
23642 self.buffer().read(cx).all_buffers().into_iter().fold(
23643 HashMap::default(),
23644 |mut acc, buffer| {
23645 let buffer = buffer.read(cx);
23646 let language = buffer.language().map(|language| language.name());
23647 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
23648 let file = buffer.file();
23649 v.insert(language_settings(language, file, cx).into_owned());
23650 }
23651 acc
23652 },
23653 )
23654 }
23655
23656 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23657 let new_language_settings = self.fetch_applicable_language_settings(cx);
23658 let language_settings_changed = new_language_settings != self.applicable_language_settings;
23659 self.applicable_language_settings = new_language_settings;
23660
23661 let new_accents = self.fetch_accent_data(cx);
23662 let accents_changed = new_accents != self.accent_data;
23663 self.accent_data = new_accents;
23664
23665 if self.diagnostics_enabled() {
23666 let new_severity = EditorSettings::get_global(cx)
23667 .diagnostics_max_severity
23668 .unwrap_or(DiagnosticSeverity::Hint);
23669 self.set_max_diagnostics_severity(new_severity, cx);
23670 }
23671 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23672 self.update_edit_prediction_settings(cx);
23673 self.refresh_edit_prediction(true, false, window, cx);
23674 self.refresh_inline_values(cx);
23675 self.refresh_inlay_hints(
23676 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
23677 self.selections.newest_anchor().head(),
23678 &self.buffer.read(cx).snapshot(cx),
23679 cx,
23680 )),
23681 cx,
23682 );
23683
23684 let old_cursor_shape = self.cursor_shape;
23685 let old_show_breadcrumbs = self.show_breadcrumbs;
23686
23687 {
23688 let editor_settings = EditorSettings::get_global(cx);
23689 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
23690 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
23691 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
23692 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
23693 }
23694
23695 if old_cursor_shape != self.cursor_shape {
23696 cx.emit(EditorEvent::CursorShapeChanged);
23697 }
23698
23699 if old_show_breadcrumbs != self.show_breadcrumbs {
23700 cx.emit(EditorEvent::BreadcrumbsChanged);
23701 }
23702
23703 let project_settings = ProjectSettings::get_global(cx);
23704 self.buffer_serialization = self
23705 .should_serialize_buffer()
23706 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
23707
23708 if self.mode.is_full() {
23709 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
23710 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
23711 if self.show_inline_diagnostics != show_inline_diagnostics {
23712 self.show_inline_diagnostics = show_inline_diagnostics;
23713 self.refresh_inline_diagnostics(false, window, cx);
23714 }
23715
23716 if self.git_blame_inline_enabled != inline_blame_enabled {
23717 self.toggle_git_blame_inline_internal(false, window, cx);
23718 }
23719
23720 let minimap_settings = EditorSettings::get_global(cx).minimap;
23721 if self.minimap_visibility != MinimapVisibility::Disabled {
23722 if self.minimap_visibility.settings_visibility()
23723 != minimap_settings.minimap_enabled()
23724 {
23725 self.set_minimap_visibility(
23726 MinimapVisibility::for_mode(self.mode(), cx),
23727 window,
23728 cx,
23729 );
23730 } else if let Some(minimap_entity) = self.minimap.as_ref() {
23731 minimap_entity.update(cx, |minimap_editor, cx| {
23732 minimap_editor.update_minimap_configuration(minimap_settings, cx)
23733 })
23734 }
23735 }
23736
23737 if language_settings_changed || accents_changed {
23738 self.colorize_brackets(true, cx);
23739 }
23740
23741 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
23742 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
23743 }) {
23744 if !inlay_splice.is_empty() {
23745 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
23746 }
23747 self.refresh_colors_for_visible_range(None, window, cx);
23748 }
23749 }
23750
23751 cx.notify();
23752 }
23753
23754 pub fn set_searchable(&mut self, searchable: bool) {
23755 self.searchable = searchable;
23756 }
23757
23758 pub fn searchable(&self) -> bool {
23759 self.searchable
23760 }
23761
23762 pub fn open_excerpts_in_split(
23763 &mut self,
23764 _: &OpenExcerptsSplit,
23765 window: &mut Window,
23766 cx: &mut Context<Self>,
23767 ) {
23768 self.open_excerpts_common(None, true, window, cx)
23769 }
23770
23771 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
23772 self.open_excerpts_common(None, false, window, cx)
23773 }
23774
23775 fn open_excerpts_common(
23776 &mut self,
23777 jump_data: Option<JumpData>,
23778 split: bool,
23779 window: &mut Window,
23780 cx: &mut Context<Self>,
23781 ) {
23782 let Some(workspace) = self.workspace() else {
23783 cx.propagate();
23784 return;
23785 };
23786
23787 if self.buffer.read(cx).is_singleton() {
23788 cx.propagate();
23789 return;
23790 }
23791
23792 let mut new_selections_by_buffer = HashMap::default();
23793 match &jump_data {
23794 Some(JumpData::MultiBufferPoint {
23795 excerpt_id,
23796 position,
23797 anchor,
23798 line_offset_from_top,
23799 }) => {
23800 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
23801 if let Some(buffer) = multi_buffer_snapshot
23802 .buffer_id_for_excerpt(*excerpt_id)
23803 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
23804 {
23805 let buffer_snapshot = buffer.read(cx).snapshot();
23806 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
23807 language::ToPoint::to_point(anchor, &buffer_snapshot)
23808 } else {
23809 buffer_snapshot.clip_point(*position, Bias::Left)
23810 };
23811 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
23812 new_selections_by_buffer.insert(
23813 buffer,
23814 (
23815 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
23816 Some(*line_offset_from_top),
23817 ),
23818 );
23819 }
23820 }
23821 Some(JumpData::MultiBufferRow {
23822 row,
23823 line_offset_from_top,
23824 }) => {
23825 let point = MultiBufferPoint::new(row.0, 0);
23826 if let Some((buffer, buffer_point, _)) =
23827 self.buffer.read(cx).point_to_buffer_point(point, cx)
23828 {
23829 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
23830 new_selections_by_buffer
23831 .entry(buffer)
23832 .or_insert((Vec::new(), Some(*line_offset_from_top)))
23833 .0
23834 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
23835 }
23836 }
23837 None => {
23838 let selections = self
23839 .selections
23840 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
23841 let multi_buffer = self.buffer.read(cx);
23842 for selection in selections {
23843 for (snapshot, range, _, anchor) in multi_buffer
23844 .snapshot(cx)
23845 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
23846 {
23847 if let Some(anchor) = anchor {
23848 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
23849 else {
23850 continue;
23851 };
23852 let offset = text::ToOffset::to_offset(
23853 &anchor.text_anchor,
23854 &buffer_handle.read(cx).snapshot(),
23855 );
23856 let range = BufferOffset(offset)..BufferOffset(offset);
23857 new_selections_by_buffer
23858 .entry(buffer_handle)
23859 .or_insert((Vec::new(), None))
23860 .0
23861 .push(range)
23862 } else {
23863 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
23864 else {
23865 continue;
23866 };
23867 new_selections_by_buffer
23868 .entry(buffer_handle)
23869 .or_insert((Vec::new(), None))
23870 .0
23871 .push(range)
23872 }
23873 }
23874 }
23875 }
23876 }
23877
23878 new_selections_by_buffer
23879 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
23880
23881 if new_selections_by_buffer.is_empty() {
23882 return;
23883 }
23884
23885 // We defer the pane interaction because we ourselves are a workspace item
23886 // and activating a new item causes the pane to call a method on us reentrantly,
23887 // which panics if we're on the stack.
23888 window.defer(cx, move |window, cx| {
23889 workspace.update(cx, |workspace, cx| {
23890 let pane = if split {
23891 workspace.adjacent_pane(window, cx)
23892 } else {
23893 workspace.active_pane().clone()
23894 };
23895
23896 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
23897 let buffer_read = buffer.read(cx);
23898 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
23899 (true, project::File::from_dyn(Some(file)).is_some())
23900 } else {
23901 (false, false)
23902 };
23903
23904 // If project file is none workspace.open_project_item will fail to open the excerpt
23905 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
23906 // so we check if there's a tab match in that case first
23907 let editor = (!has_file || !is_project_file)
23908 .then(|| {
23909 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
23910 // so `workspace.open_project_item` will never find them, always opening a new editor.
23911 // Instead, we try to activate the existing editor in the pane first.
23912 let (editor, pane_item_index, pane_item_id) =
23913 pane.read(cx).items().enumerate().find_map(|(i, item)| {
23914 let editor = item.downcast::<Editor>()?;
23915 let singleton_buffer =
23916 editor.read(cx).buffer().read(cx).as_singleton()?;
23917 if singleton_buffer == buffer {
23918 Some((editor, i, item.item_id()))
23919 } else {
23920 None
23921 }
23922 })?;
23923 pane.update(cx, |pane, cx| {
23924 pane.activate_item(pane_item_index, true, true, window, cx);
23925 if !PreviewTabsSettings::get_global(cx)
23926 .enable_preview_from_multibuffer
23927 {
23928 pane.unpreview_item_if_preview(pane_item_id);
23929 }
23930 });
23931 Some(editor)
23932 })
23933 .flatten()
23934 .unwrap_or_else(|| {
23935 let keep_old_preview = PreviewTabsSettings::get_global(cx)
23936 .enable_keep_preview_on_code_navigation;
23937 let allow_new_preview =
23938 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
23939 workspace.open_project_item::<Self>(
23940 pane.clone(),
23941 buffer,
23942 true,
23943 true,
23944 keep_old_preview,
23945 allow_new_preview,
23946 window,
23947 cx,
23948 )
23949 });
23950
23951 editor.update(cx, |editor, cx| {
23952 if has_file && !is_project_file {
23953 editor.set_read_only(true);
23954 }
23955 let autoscroll = match scroll_offset {
23956 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
23957 None => Autoscroll::newest(),
23958 };
23959 let nav_history = editor.nav_history.take();
23960 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23961 let Some((&excerpt_id, _, buffer_snapshot)) =
23962 multibuffer_snapshot.as_singleton()
23963 else {
23964 return;
23965 };
23966 editor.change_selections(
23967 SelectionEffects::scroll(autoscroll),
23968 window,
23969 cx,
23970 |s| {
23971 s.select_ranges(ranges.into_iter().map(|range| {
23972 let range = buffer_snapshot.anchor_before(range.start)
23973 ..buffer_snapshot.anchor_after(range.end);
23974 multibuffer_snapshot
23975 .anchor_range_in_excerpt(excerpt_id, range)
23976 .unwrap()
23977 }));
23978 },
23979 );
23980 editor.nav_history = nav_history;
23981 });
23982 }
23983 })
23984 });
23985 }
23986
23987 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
23988 let snapshot = self.buffer.read(cx).read(cx);
23989 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
23990 Some(
23991 ranges
23992 .iter()
23993 .map(move |range| {
23994 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
23995 })
23996 .collect(),
23997 )
23998 }
23999
24000 fn selection_replacement_ranges(
24001 &self,
24002 range: Range<MultiBufferOffsetUtf16>,
24003 cx: &mut App,
24004 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24005 let selections = self
24006 .selections
24007 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24008 let newest_selection = selections
24009 .iter()
24010 .max_by_key(|selection| selection.id)
24011 .unwrap();
24012 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24013 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24014 let snapshot = self.buffer.read(cx).read(cx);
24015 selections
24016 .into_iter()
24017 .map(|mut selection| {
24018 selection.start.0.0 =
24019 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24020 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24021 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24022 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24023 })
24024 .collect()
24025 }
24026
24027 fn report_editor_event(
24028 &self,
24029 reported_event: ReportEditorEvent,
24030 file_extension: Option<String>,
24031 cx: &App,
24032 ) {
24033 if cfg!(any(test, feature = "test-support")) {
24034 return;
24035 }
24036
24037 let Some(project) = &self.project else { return };
24038
24039 // If None, we are in a file without an extension
24040 let file = self
24041 .buffer
24042 .read(cx)
24043 .as_singleton()
24044 .and_then(|b| b.read(cx).file());
24045 let file_extension = file_extension.or(file
24046 .as_ref()
24047 .and_then(|file| Path::new(file.file_name(cx)).extension())
24048 .and_then(|e| e.to_str())
24049 .map(|a| a.to_string()));
24050
24051 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24052 .map(|vim_mode| vim_mode.0)
24053 .unwrap_or(false);
24054
24055 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24056 let copilot_enabled = edit_predictions_provider
24057 == language::language_settings::EditPredictionProvider::Copilot;
24058 let copilot_enabled_for_language = self
24059 .buffer
24060 .read(cx)
24061 .language_settings(cx)
24062 .show_edit_predictions;
24063
24064 let project = project.read(cx);
24065 let event_type = reported_event.event_type();
24066
24067 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24068 telemetry::event!(
24069 event_type,
24070 type = if auto_saved {"autosave"} else {"manual"},
24071 file_extension,
24072 vim_mode,
24073 copilot_enabled,
24074 copilot_enabled_for_language,
24075 edit_predictions_provider,
24076 is_via_ssh = project.is_via_remote_server(),
24077 );
24078 } else {
24079 telemetry::event!(
24080 event_type,
24081 file_extension,
24082 vim_mode,
24083 copilot_enabled,
24084 copilot_enabled_for_language,
24085 edit_predictions_provider,
24086 is_via_ssh = project.is_via_remote_server(),
24087 );
24088 };
24089 }
24090
24091 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24092 /// with each line being an array of {text, highlight} objects.
24093 fn copy_highlight_json(
24094 &mut self,
24095 _: &CopyHighlightJson,
24096 window: &mut Window,
24097 cx: &mut Context<Self>,
24098 ) {
24099 #[derive(Serialize)]
24100 struct Chunk<'a> {
24101 text: String,
24102 highlight: Option<&'a str>,
24103 }
24104
24105 let snapshot = self.buffer.read(cx).snapshot(cx);
24106 let range = self
24107 .selected_text_range(false, window, cx)
24108 .and_then(|selection| {
24109 if selection.range.is_empty() {
24110 None
24111 } else {
24112 Some(
24113 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24114 selection.range.start,
24115 )))
24116 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24117 selection.range.end,
24118 ))),
24119 )
24120 }
24121 })
24122 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24123
24124 let chunks = snapshot.chunks(range, true);
24125 let mut lines = Vec::new();
24126 let mut line: VecDeque<Chunk> = VecDeque::new();
24127
24128 let Some(style) = self.style.as_ref() else {
24129 return;
24130 };
24131
24132 for chunk in chunks {
24133 let highlight = chunk
24134 .syntax_highlight_id
24135 .and_then(|id| id.name(&style.syntax));
24136 let mut chunk_lines = chunk.text.split('\n').peekable();
24137 while let Some(text) = chunk_lines.next() {
24138 let mut merged_with_last_token = false;
24139 if let Some(last_token) = line.back_mut()
24140 && last_token.highlight == highlight
24141 {
24142 last_token.text.push_str(text);
24143 merged_with_last_token = true;
24144 }
24145
24146 if !merged_with_last_token {
24147 line.push_back(Chunk {
24148 text: text.into(),
24149 highlight,
24150 });
24151 }
24152
24153 if chunk_lines.peek().is_some() {
24154 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24155 line.pop_front();
24156 }
24157 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24158 line.pop_back();
24159 }
24160
24161 lines.push(mem::take(&mut line));
24162 }
24163 }
24164 }
24165
24166 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24167 return;
24168 };
24169 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24170 }
24171
24172 pub fn open_context_menu(
24173 &mut self,
24174 _: &OpenContextMenu,
24175 window: &mut Window,
24176 cx: &mut Context<Self>,
24177 ) {
24178 self.request_autoscroll(Autoscroll::newest(), cx);
24179 let position = self
24180 .selections
24181 .newest_display(&self.display_snapshot(cx))
24182 .start;
24183 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24184 }
24185
24186 pub fn replay_insert_event(
24187 &mut self,
24188 text: &str,
24189 relative_utf16_range: Option<Range<isize>>,
24190 window: &mut Window,
24191 cx: &mut Context<Self>,
24192 ) {
24193 if !self.input_enabled {
24194 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24195 return;
24196 }
24197 if let Some(relative_utf16_range) = relative_utf16_range {
24198 let selections = self
24199 .selections
24200 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24201 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24202 let new_ranges = selections.into_iter().map(|range| {
24203 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24204 range
24205 .head()
24206 .0
24207 .0
24208 .saturating_add_signed(relative_utf16_range.start),
24209 ));
24210 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24211 range
24212 .head()
24213 .0
24214 .0
24215 .saturating_add_signed(relative_utf16_range.end),
24216 ));
24217 start..end
24218 });
24219 s.select_ranges(new_ranges);
24220 });
24221 }
24222
24223 self.handle_input(text, window, cx);
24224 }
24225
24226 pub fn is_focused(&self, window: &Window) -> bool {
24227 self.focus_handle.is_focused(window)
24228 }
24229
24230 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24231 cx.emit(EditorEvent::Focused);
24232
24233 if let Some(descendant) = self
24234 .last_focused_descendant
24235 .take()
24236 .and_then(|descendant| descendant.upgrade())
24237 {
24238 window.focus(&descendant, cx);
24239 } else {
24240 if let Some(blame) = self.blame.as_ref() {
24241 blame.update(cx, GitBlame::focus)
24242 }
24243
24244 self.blink_manager.update(cx, BlinkManager::enable);
24245 self.show_cursor_names(window, cx);
24246 self.buffer.update(cx, |buffer, cx| {
24247 buffer.finalize_last_transaction(cx);
24248 if self.leader_id.is_none() {
24249 buffer.set_active_selections(
24250 &self.selections.disjoint_anchors_arc(),
24251 self.selections.line_mode(),
24252 self.cursor_shape,
24253 cx,
24254 );
24255 }
24256 });
24257
24258 if let Some(position_map) = self.last_position_map.clone() {
24259 EditorElement::mouse_moved(
24260 self,
24261 &MouseMoveEvent {
24262 position: window.mouse_position(),
24263 pressed_button: None,
24264 modifiers: window.modifiers(),
24265 },
24266 &position_map,
24267 window,
24268 cx,
24269 );
24270 }
24271 }
24272 }
24273
24274 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24275 cx.emit(EditorEvent::FocusedIn)
24276 }
24277
24278 fn handle_focus_out(
24279 &mut self,
24280 event: FocusOutEvent,
24281 _window: &mut Window,
24282 cx: &mut Context<Self>,
24283 ) {
24284 if event.blurred != self.focus_handle {
24285 self.last_focused_descendant = Some(event.blurred);
24286 }
24287 self.selection_drag_state = SelectionDragState::None;
24288 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24289 }
24290
24291 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24292 self.blink_manager.update(cx, BlinkManager::disable);
24293 self.buffer
24294 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24295
24296 if let Some(blame) = self.blame.as_ref() {
24297 blame.update(cx, GitBlame::blur)
24298 }
24299 if !self.hover_state.focused(window, cx) {
24300 hide_hover(self, cx);
24301 }
24302 if !self
24303 .context_menu
24304 .borrow()
24305 .as_ref()
24306 .is_some_and(|context_menu| context_menu.focused(window, cx))
24307 {
24308 self.hide_context_menu(window, cx);
24309 }
24310 self.take_active_edit_prediction(cx);
24311 cx.emit(EditorEvent::Blurred);
24312 cx.notify();
24313 }
24314
24315 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24316 let mut pending: String = window
24317 .pending_input_keystrokes()
24318 .into_iter()
24319 .flatten()
24320 .filter_map(|keystroke| keystroke.key_char.clone())
24321 .collect();
24322
24323 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24324 pending = "".to_string();
24325 }
24326
24327 let existing_pending = self
24328 .text_highlights::<PendingInput>(cx)
24329 .map(|(_, ranges)| ranges.to_vec());
24330 if existing_pending.is_none() && pending.is_empty() {
24331 return;
24332 }
24333 let transaction =
24334 self.transact(window, cx, |this, window, cx| {
24335 let selections = this
24336 .selections
24337 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24338 let edits = selections
24339 .iter()
24340 .map(|selection| (selection.end..selection.end, pending.clone()));
24341 this.edit(edits, cx);
24342 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24343 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24344 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24345 }));
24346 });
24347 if let Some(existing_ranges) = existing_pending {
24348 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24349 this.edit(edits, cx);
24350 }
24351 });
24352
24353 let snapshot = self.snapshot(window, cx);
24354 let ranges = self
24355 .selections
24356 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24357 .into_iter()
24358 .map(|selection| {
24359 snapshot.buffer_snapshot().anchor_after(selection.end)
24360 ..snapshot
24361 .buffer_snapshot()
24362 .anchor_before(selection.end + pending.len())
24363 })
24364 .collect();
24365
24366 if pending.is_empty() {
24367 self.clear_highlights::<PendingInput>(cx);
24368 } else {
24369 self.highlight_text::<PendingInput>(
24370 ranges,
24371 HighlightStyle {
24372 underline: Some(UnderlineStyle {
24373 thickness: px(1.),
24374 color: None,
24375 wavy: false,
24376 }),
24377 ..Default::default()
24378 },
24379 cx,
24380 );
24381 }
24382
24383 self.ime_transaction = self.ime_transaction.or(transaction);
24384 if let Some(transaction) = self.ime_transaction {
24385 self.buffer.update(cx, |buffer, cx| {
24386 buffer.group_until_transaction(transaction, cx);
24387 });
24388 }
24389
24390 if self.text_highlights::<PendingInput>(cx).is_none() {
24391 self.ime_transaction.take();
24392 }
24393 }
24394
24395 pub fn register_action_renderer(
24396 &mut self,
24397 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24398 ) -> Subscription {
24399 let id = self.next_editor_action_id.post_inc();
24400 self.editor_actions
24401 .borrow_mut()
24402 .insert(id, Box::new(listener));
24403
24404 let editor_actions = self.editor_actions.clone();
24405 Subscription::new(move || {
24406 editor_actions.borrow_mut().remove(&id);
24407 })
24408 }
24409
24410 pub fn register_action<A: Action>(
24411 &mut self,
24412 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24413 ) -> Subscription {
24414 let id = self.next_editor_action_id.post_inc();
24415 let listener = Arc::new(listener);
24416 self.editor_actions.borrow_mut().insert(
24417 id,
24418 Box::new(move |_, window, _| {
24419 let listener = listener.clone();
24420 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24421 let action = action.downcast_ref().unwrap();
24422 if phase == DispatchPhase::Bubble {
24423 listener(action, window, cx)
24424 }
24425 })
24426 }),
24427 );
24428
24429 let editor_actions = self.editor_actions.clone();
24430 Subscription::new(move || {
24431 editor_actions.borrow_mut().remove(&id);
24432 })
24433 }
24434
24435 pub fn file_header_size(&self) -> u32 {
24436 FILE_HEADER_HEIGHT
24437 }
24438
24439 pub fn restore(
24440 &mut self,
24441 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24442 window: &mut Window,
24443 cx: &mut Context<Self>,
24444 ) {
24445 self.buffer().update(cx, |multi_buffer, cx| {
24446 for (buffer_id, changes) in revert_changes {
24447 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24448 buffer.update(cx, |buffer, cx| {
24449 buffer.edit(
24450 changes
24451 .into_iter()
24452 .map(|(range, text)| (range, text.to_string())),
24453 None,
24454 cx,
24455 );
24456 });
24457 }
24458 }
24459 });
24460 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24461 selections.refresh()
24462 });
24463 }
24464
24465 pub fn to_pixel_point(
24466 &mut self,
24467 source: multi_buffer::Anchor,
24468 editor_snapshot: &EditorSnapshot,
24469 window: &mut Window,
24470 cx: &App,
24471 ) -> Option<gpui::Point<Pixels>> {
24472 let source_point = source.to_display_point(editor_snapshot);
24473 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
24474 }
24475
24476 pub fn display_to_pixel_point(
24477 &mut self,
24478 source: DisplayPoint,
24479 editor_snapshot: &EditorSnapshot,
24480 window: &mut Window,
24481 cx: &App,
24482 ) -> Option<gpui::Point<Pixels>> {
24483 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
24484 let text_layout_details = self.text_layout_details(window);
24485 let scroll_top = text_layout_details
24486 .scroll_anchor
24487 .scroll_position(editor_snapshot)
24488 .y;
24489
24490 if source.row().as_f64() < scroll_top.floor() {
24491 return None;
24492 }
24493 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
24494 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
24495 Some(gpui::Point::new(source_x, source_y))
24496 }
24497
24498 pub fn has_visible_completions_menu(&self) -> bool {
24499 !self.edit_prediction_preview_is_active()
24500 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
24501 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
24502 })
24503 }
24504
24505 pub fn register_addon<T: Addon>(&mut self, instance: T) {
24506 if self.mode.is_minimap() {
24507 return;
24508 }
24509 self.addons
24510 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
24511 }
24512
24513 pub fn unregister_addon<T: Addon>(&mut self) {
24514 self.addons.remove(&std::any::TypeId::of::<T>());
24515 }
24516
24517 pub fn addon<T: Addon>(&self) -> Option<&T> {
24518 let type_id = std::any::TypeId::of::<T>();
24519 self.addons
24520 .get(&type_id)
24521 .and_then(|item| item.to_any().downcast_ref::<T>())
24522 }
24523
24524 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
24525 let type_id = std::any::TypeId::of::<T>();
24526 self.addons
24527 .get_mut(&type_id)
24528 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
24529 }
24530
24531 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
24532 let text_layout_details = self.text_layout_details(window);
24533 let style = &text_layout_details.editor_style;
24534 let font_id = window.text_system().resolve_font(&style.text.font());
24535 let font_size = style.text.font_size.to_pixels(window.rem_size());
24536 let line_height = style.text.line_height_in_pixels(window.rem_size());
24537 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
24538 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
24539
24540 CharacterDimensions {
24541 em_width,
24542 em_advance,
24543 line_height,
24544 }
24545 }
24546
24547 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
24548 self.load_diff_task.clone()
24549 }
24550
24551 fn read_metadata_from_db(
24552 &mut self,
24553 item_id: u64,
24554 workspace_id: WorkspaceId,
24555 window: &mut Window,
24556 cx: &mut Context<Editor>,
24557 ) {
24558 if self.buffer_kind(cx) == ItemBufferKind::Singleton
24559 && !self.mode.is_minimap()
24560 && WorkspaceSettings::get(None, cx).restore_on_startup
24561 != RestoreOnStartupBehavior::EmptyTab
24562 {
24563 let buffer_snapshot = OnceCell::new();
24564
24565 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
24566 && !folds.is_empty()
24567 {
24568 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24569 let snapshot_len = snapshot.len().0;
24570
24571 // Helper: search for fingerprint in buffer, return offset if found
24572 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
24573 // Ensure we start at a character boundary (defensive)
24574 let search_start = snapshot
24575 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
24576 .0;
24577 let search_end = snapshot_len.saturating_sub(fingerprint.len());
24578
24579 let mut byte_offset = search_start;
24580 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
24581 if byte_offset > search_end {
24582 break;
24583 }
24584 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
24585 return Some(byte_offset);
24586 }
24587 byte_offset += ch.len_utf8();
24588 }
24589 None
24590 };
24591
24592 // Track search position to handle duplicate fingerprints correctly.
24593 // Folds are stored in document order, so we advance after each match.
24594 let mut search_start = 0usize;
24595
24596 let valid_folds: Vec<_> = folds
24597 .into_iter()
24598 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
24599 // Skip folds without fingerprints (old data before migration)
24600 let sfp = start_fp?;
24601 let efp = end_fp?;
24602 let efp_len = efp.len();
24603
24604 // Fast path: check if fingerprints match at stored offsets
24605 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
24606 let start_matches = stored_start < snapshot_len
24607 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
24608 let efp_check_pos = stored_end.saturating_sub(efp_len);
24609 let end_matches = efp_check_pos >= stored_start
24610 && stored_end <= snapshot_len
24611 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
24612
24613 let (new_start, new_end) = if start_matches && end_matches {
24614 // Offsets unchanged, use stored values
24615 (stored_start, stored_end)
24616 } else if sfp == efp {
24617 // Short fold: identical fingerprints can only match once per search
24618 // Use stored fold length to compute new_end
24619 let new_start = find_fingerprint(&sfp, search_start)?;
24620 let fold_len = stored_end - stored_start;
24621 let new_end = new_start + fold_len;
24622 (new_start, new_end)
24623 } else {
24624 // Slow path: search for fingerprints in buffer
24625 let new_start = find_fingerprint(&sfp, search_start)?;
24626 // Search for end_fp after start, then add efp_len to get actual fold end
24627 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
24628 let new_end = efp_pos + efp_len;
24629 (new_start, new_end)
24630 };
24631
24632 // Advance search position for next fold
24633 search_start = new_end;
24634
24635 // Validate fold makes sense (end must be after start)
24636 if new_end <= new_start {
24637 return None;
24638 }
24639
24640 Some(
24641 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
24642 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
24643 )
24644 })
24645 .collect();
24646
24647 if !valid_folds.is_empty() {
24648 self.fold_ranges(valid_folds, false, window, cx);
24649
24650 // Migrate folds to current entity_id before workspace cleanup runs.
24651 // Entity IDs change between sessions, but workspace cleanup deletes
24652 // old editor rows (cascading to folds) based on current entity IDs.
24653 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
24654 if new_editor_id != item_id {
24655 cx.spawn(async move |_, _| {
24656 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
24657 .await
24658 .log_err();
24659 })
24660 .detach();
24661 }
24662 }
24663 }
24664
24665 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
24666 && !selections.is_empty()
24667 {
24668 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24669 // skip adding the initial selection to selection history
24670 self.selection_history.mode = SelectionHistoryMode::Skipping;
24671 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24672 s.select_ranges(selections.into_iter().map(|(start, end)| {
24673 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
24674 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
24675 }));
24676 });
24677 self.selection_history.mode = SelectionHistoryMode::Normal;
24678 };
24679 }
24680
24681 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
24682 }
24683
24684 fn update_lsp_data(
24685 &mut self,
24686 for_buffer: Option<BufferId>,
24687 window: &mut Window,
24688 cx: &mut Context<'_, Self>,
24689 ) {
24690 self.pull_diagnostics(for_buffer, window, cx);
24691 self.refresh_colors_for_visible_range(for_buffer, window, cx);
24692 }
24693
24694 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
24695 if self.ignore_lsp_data() {
24696 return;
24697 }
24698 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
24699 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
24700 }
24701 }
24702
24703 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
24704 if self.ignore_lsp_data() {
24705 return;
24706 }
24707
24708 if !self.registered_buffers.contains_key(&buffer_id)
24709 && let Some(project) = self.project.as_ref()
24710 {
24711 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
24712 project.update(cx, |project, cx| {
24713 self.registered_buffers.insert(
24714 buffer_id,
24715 project.register_buffer_with_language_servers(&buffer, cx),
24716 );
24717 });
24718 } else {
24719 self.registered_buffers.remove(&buffer_id);
24720 }
24721 }
24722 }
24723
24724 fn ignore_lsp_data(&self) -> bool {
24725 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
24726 // skip any LSP updates for it.
24727 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
24728 }
24729
24730 fn create_style(&self, cx: &App) -> EditorStyle {
24731 let settings = ThemeSettings::get_global(cx);
24732
24733 let mut text_style = match self.mode {
24734 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24735 color: cx.theme().colors().editor_foreground,
24736 font_family: settings.ui_font.family.clone(),
24737 font_features: settings.ui_font.features.clone(),
24738 font_fallbacks: settings.ui_font.fallbacks.clone(),
24739 font_size: rems(0.875).into(),
24740 font_weight: settings.ui_font.weight,
24741 line_height: relative(settings.buffer_line_height.value()),
24742 ..Default::default()
24743 },
24744 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24745 color: cx.theme().colors().editor_foreground,
24746 font_family: settings.buffer_font.family.clone(),
24747 font_features: settings.buffer_font.features.clone(),
24748 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24749 font_size: settings.buffer_font_size(cx).into(),
24750 font_weight: settings.buffer_font.weight,
24751 line_height: relative(settings.buffer_line_height.value()),
24752 ..Default::default()
24753 },
24754 };
24755 if let Some(text_style_refinement) = &self.text_style_refinement {
24756 text_style.refine(text_style_refinement)
24757 }
24758
24759 let background = match self.mode {
24760 EditorMode::SingleLine => cx.theme().system().transparent,
24761 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24762 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24763 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24764 };
24765
24766 EditorStyle {
24767 background,
24768 border: cx.theme().colors().border,
24769 local_player: cx.theme().players().local(),
24770 text: text_style,
24771 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24772 syntax: cx.theme().syntax().clone(),
24773 status: cx.theme().status().clone(),
24774 inlay_hints_style: make_inlay_hints_style(cx),
24775 edit_prediction_styles: make_suggestion_styles(cx),
24776 unnecessary_code_fade: settings.unnecessary_code_fade,
24777 show_underlines: self.diagnostics_enabled(),
24778 }
24779 }
24780 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
24781 let cursor = self.selections.newest_anchor().head();
24782 let multibuffer = self.buffer().read(cx);
24783 let is_singleton = multibuffer.is_singleton();
24784 let (buffer_id, symbols) = multibuffer
24785 .read(cx)
24786 .symbols_containing(cursor, Some(variant.syntax()))?;
24787 let buffer = multibuffer.buffer(buffer_id)?;
24788
24789 let buffer = buffer.read(cx);
24790 let settings = ThemeSettings::get_global(cx);
24791 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
24792 let mut breadcrumbs = if is_singleton {
24793 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
24794 buffer
24795 .snapshot()
24796 .resolve_file_path(
24797 self.project
24798 .as_ref()
24799 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
24800 .unwrap_or_default(),
24801 cx,
24802 )
24803 .unwrap_or_else(|| {
24804 if multibuffer.is_singleton() {
24805 multibuffer.title(cx).to_string()
24806 } else {
24807 "untitled".to_string()
24808 }
24809 })
24810 });
24811 vec![BreadcrumbText {
24812 text,
24813 highlights: None,
24814 font: Some(settings.buffer_font.clone()),
24815 }]
24816 } else {
24817 vec![]
24818 };
24819
24820 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
24821 text: symbol.text,
24822 highlights: Some(symbol.highlight_ranges),
24823 font: Some(settings.buffer_font.clone()),
24824 }));
24825 Some(breadcrumbs)
24826 }
24827}
24828
24829fn edit_for_markdown_paste<'a>(
24830 buffer: &MultiBufferSnapshot,
24831 range: Range<MultiBufferOffset>,
24832 to_insert: &'a str,
24833 url: Option<url::Url>,
24834) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
24835 if url.is_none() {
24836 return (range, Cow::Borrowed(to_insert));
24837 };
24838
24839 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
24840
24841 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
24842 Cow::Borrowed(to_insert)
24843 } else {
24844 Cow::Owned(format!("[{old_text}]({to_insert})"))
24845 };
24846 (range, new_text)
24847}
24848
24849fn process_completion_for_edit(
24850 completion: &Completion,
24851 intent: CompletionIntent,
24852 buffer: &Entity<Buffer>,
24853 cursor_position: &text::Anchor,
24854 cx: &mut Context<Editor>,
24855) -> CompletionEdit {
24856 let buffer = buffer.read(cx);
24857 let buffer_snapshot = buffer.snapshot();
24858 let (snippet, new_text) = if completion.is_snippet() {
24859 let mut snippet_source = completion.new_text.clone();
24860 // Workaround for typescript language server issues so that methods don't expand within
24861 // strings and functions with type expressions. The previous point is used because the query
24862 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
24863 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
24864 let previous_point = if previous_point.column > 0 {
24865 cursor_position.to_previous_offset(&buffer_snapshot)
24866 } else {
24867 cursor_position.to_offset(&buffer_snapshot)
24868 };
24869 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
24870 && scope.prefers_label_for_snippet_in_completion()
24871 && let Some(label) = completion.label()
24872 && matches!(
24873 completion.kind(),
24874 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
24875 )
24876 {
24877 snippet_source = label;
24878 }
24879 match Snippet::parse(&snippet_source).log_err() {
24880 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
24881 None => (None, completion.new_text.clone()),
24882 }
24883 } else {
24884 (None, completion.new_text.clone())
24885 };
24886
24887 let mut range_to_replace = {
24888 let replace_range = &completion.replace_range;
24889 if let CompletionSource::Lsp {
24890 insert_range: Some(insert_range),
24891 ..
24892 } = &completion.source
24893 {
24894 debug_assert_eq!(
24895 insert_range.start, replace_range.start,
24896 "insert_range and replace_range should start at the same position"
24897 );
24898 debug_assert!(
24899 insert_range
24900 .start
24901 .cmp(cursor_position, &buffer_snapshot)
24902 .is_le(),
24903 "insert_range should start before or at cursor position"
24904 );
24905 debug_assert!(
24906 replace_range
24907 .start
24908 .cmp(cursor_position, &buffer_snapshot)
24909 .is_le(),
24910 "replace_range should start before or at cursor position"
24911 );
24912
24913 let should_replace = match intent {
24914 CompletionIntent::CompleteWithInsert => false,
24915 CompletionIntent::CompleteWithReplace => true,
24916 CompletionIntent::Complete | CompletionIntent::Compose => {
24917 let insert_mode =
24918 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
24919 .completions
24920 .lsp_insert_mode;
24921 match insert_mode {
24922 LspInsertMode::Insert => false,
24923 LspInsertMode::Replace => true,
24924 LspInsertMode::ReplaceSubsequence => {
24925 let mut text_to_replace = buffer.chars_for_range(
24926 buffer.anchor_before(replace_range.start)
24927 ..buffer.anchor_after(replace_range.end),
24928 );
24929 let mut current_needle = text_to_replace.next();
24930 for haystack_ch in completion.label.text.chars() {
24931 if let Some(needle_ch) = current_needle
24932 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
24933 {
24934 current_needle = text_to_replace.next();
24935 }
24936 }
24937 current_needle.is_none()
24938 }
24939 LspInsertMode::ReplaceSuffix => {
24940 if replace_range
24941 .end
24942 .cmp(cursor_position, &buffer_snapshot)
24943 .is_gt()
24944 {
24945 let range_after_cursor = *cursor_position..replace_range.end;
24946 let text_after_cursor = buffer
24947 .text_for_range(
24948 buffer.anchor_before(range_after_cursor.start)
24949 ..buffer.anchor_after(range_after_cursor.end),
24950 )
24951 .collect::<String>()
24952 .to_ascii_lowercase();
24953 completion
24954 .label
24955 .text
24956 .to_ascii_lowercase()
24957 .ends_with(&text_after_cursor)
24958 } else {
24959 true
24960 }
24961 }
24962 }
24963 }
24964 };
24965
24966 if should_replace {
24967 replace_range.clone()
24968 } else {
24969 insert_range.clone()
24970 }
24971 } else {
24972 replace_range.clone()
24973 }
24974 };
24975
24976 if range_to_replace
24977 .end
24978 .cmp(cursor_position, &buffer_snapshot)
24979 .is_lt()
24980 {
24981 range_to_replace.end = *cursor_position;
24982 }
24983
24984 let replace_range = range_to_replace.to_offset(buffer);
24985 CompletionEdit {
24986 new_text,
24987 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
24988 snippet,
24989 }
24990}
24991
24992struct CompletionEdit {
24993 new_text: String,
24994 replace_range: Range<BufferOffset>,
24995 snippet: Option<Snippet>,
24996}
24997
24998fn comment_delimiter_for_newline(
24999 start_point: &Point,
25000 buffer: &MultiBufferSnapshot,
25001 language: &LanguageScope,
25002) -> Option<Arc<str>> {
25003 let delimiters = language.line_comment_prefixes();
25004 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25005 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25006
25007 let num_of_whitespaces = snapshot
25008 .chars_for_range(range.clone())
25009 .take_while(|c| c.is_whitespace())
25010 .count();
25011 let comment_candidate = snapshot
25012 .chars_for_range(range.clone())
25013 .skip(num_of_whitespaces)
25014 .take(max_len_of_delimiter)
25015 .collect::<String>();
25016 let (delimiter, trimmed_len) = delimiters
25017 .iter()
25018 .filter_map(|delimiter| {
25019 let prefix = delimiter.trim_end();
25020 if comment_candidate.starts_with(prefix) {
25021 Some((delimiter, prefix.len()))
25022 } else {
25023 None
25024 }
25025 })
25026 .max_by_key(|(_, len)| *len)?;
25027
25028 if let Some(BlockCommentConfig {
25029 start: block_start, ..
25030 }) = language.block_comment()
25031 {
25032 let block_start_trimmed = block_start.trim_end();
25033 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25034 let line_content = snapshot
25035 .chars_for_range(range)
25036 .skip(num_of_whitespaces)
25037 .take(block_start_trimmed.len())
25038 .collect::<String>();
25039
25040 if line_content.starts_with(block_start_trimmed) {
25041 return None;
25042 }
25043 }
25044 }
25045
25046 let cursor_is_placed_after_comment_marker =
25047 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25048 if cursor_is_placed_after_comment_marker {
25049 Some(delimiter.clone())
25050 } else {
25051 None
25052 }
25053}
25054
25055fn documentation_delimiter_for_newline(
25056 start_point: &Point,
25057 buffer: &MultiBufferSnapshot,
25058 language: &LanguageScope,
25059 newline_config: &mut NewlineConfig,
25060) -> Option<Arc<str>> {
25061 let BlockCommentConfig {
25062 start: start_tag,
25063 end: end_tag,
25064 prefix: delimiter,
25065 tab_size: len,
25066 } = language.documentation_comment()?;
25067 let is_within_block_comment = buffer
25068 .language_scope_at(*start_point)
25069 .is_some_and(|scope| scope.override_name() == Some("comment"));
25070 if !is_within_block_comment {
25071 return None;
25072 }
25073
25074 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25075
25076 let num_of_whitespaces = snapshot
25077 .chars_for_range(range.clone())
25078 .take_while(|c| c.is_whitespace())
25079 .count();
25080
25081 // 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.
25082 let column = start_point.column;
25083 let cursor_is_after_start_tag = {
25084 let start_tag_len = start_tag.len();
25085 let start_tag_line = snapshot
25086 .chars_for_range(range.clone())
25087 .skip(num_of_whitespaces)
25088 .take(start_tag_len)
25089 .collect::<String>();
25090 if start_tag_line.starts_with(start_tag.as_ref()) {
25091 num_of_whitespaces + start_tag_len <= column as usize
25092 } else {
25093 false
25094 }
25095 };
25096
25097 let cursor_is_after_delimiter = {
25098 let delimiter_trim = delimiter.trim_end();
25099 let delimiter_line = snapshot
25100 .chars_for_range(range.clone())
25101 .skip(num_of_whitespaces)
25102 .take(delimiter_trim.len())
25103 .collect::<String>();
25104 if delimiter_line.starts_with(delimiter_trim) {
25105 num_of_whitespaces + delimiter_trim.len() <= column as usize
25106 } else {
25107 false
25108 }
25109 };
25110
25111 let mut needs_extra_line = false;
25112 let mut extra_line_additional_indent = IndentSize::spaces(0);
25113
25114 let cursor_is_before_end_tag_if_exists = {
25115 let mut char_position = 0u32;
25116 let mut end_tag_offset = None;
25117
25118 'outer: for chunk in snapshot.text_for_range(range) {
25119 if let Some(byte_pos) = chunk.find(&**end_tag) {
25120 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25121 end_tag_offset = Some(char_position + chars_before_match);
25122 break 'outer;
25123 }
25124 char_position += chunk.chars().count() as u32;
25125 }
25126
25127 if let Some(end_tag_offset) = end_tag_offset {
25128 let cursor_is_before_end_tag = column <= end_tag_offset;
25129 if cursor_is_after_start_tag {
25130 if cursor_is_before_end_tag {
25131 needs_extra_line = true;
25132 }
25133 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25134 if cursor_is_at_start_of_end_tag {
25135 extra_line_additional_indent.len = *len;
25136 }
25137 }
25138 cursor_is_before_end_tag
25139 } else {
25140 true
25141 }
25142 };
25143
25144 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25145 && cursor_is_before_end_tag_if_exists
25146 {
25147 let additional_indent = if cursor_is_after_start_tag {
25148 IndentSize::spaces(*len)
25149 } else {
25150 IndentSize::spaces(0)
25151 };
25152
25153 *newline_config = NewlineConfig::Newline {
25154 additional_indent,
25155 extra_line_additional_indent: if needs_extra_line {
25156 Some(extra_line_additional_indent)
25157 } else {
25158 None
25159 },
25160 prevent_auto_indent: true,
25161 };
25162 Some(delimiter.clone())
25163 } else {
25164 None
25165 }
25166}
25167
25168const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25169
25170fn list_delimiter_for_newline(
25171 start_point: &Point,
25172 buffer: &MultiBufferSnapshot,
25173 language: &LanguageScope,
25174 newline_config: &mut NewlineConfig,
25175) -> Option<Arc<str>> {
25176 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25177
25178 let num_of_whitespaces = snapshot
25179 .chars_for_range(range.clone())
25180 .take_while(|c| c.is_whitespace())
25181 .count();
25182
25183 let task_list_entries: Vec<_> = language
25184 .task_list()
25185 .into_iter()
25186 .flat_map(|config| {
25187 config
25188 .prefixes
25189 .iter()
25190 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25191 })
25192 .collect();
25193 let unordered_list_entries: Vec<_> = language
25194 .unordered_list()
25195 .iter()
25196 .map(|marker| (marker.as_ref(), marker.as_ref()))
25197 .collect();
25198
25199 let all_entries: Vec<_> = task_list_entries
25200 .into_iter()
25201 .chain(unordered_list_entries)
25202 .collect();
25203
25204 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25205 let candidate: String = snapshot
25206 .chars_for_range(range.clone())
25207 .skip(num_of_whitespaces)
25208 .take(max_prefix_len)
25209 .collect();
25210
25211 if let Some((prefix, continuation)) = all_entries
25212 .iter()
25213 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25214 .max_by_key(|(prefix, _)| prefix.len())
25215 {
25216 let end_of_prefix = num_of_whitespaces + prefix.len();
25217 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25218 let has_content_after_marker = snapshot
25219 .chars_for_range(range)
25220 .skip(end_of_prefix)
25221 .any(|c| !c.is_whitespace());
25222
25223 if has_content_after_marker && cursor_is_after_prefix {
25224 return Some((*continuation).into());
25225 }
25226
25227 if start_point.column as usize == end_of_prefix {
25228 if num_of_whitespaces == 0 {
25229 *newline_config = NewlineConfig::ClearCurrentLine;
25230 } else {
25231 *newline_config = NewlineConfig::UnindentCurrentLine {
25232 continuation: (*continuation).into(),
25233 };
25234 }
25235 }
25236
25237 return None;
25238 }
25239 }
25240
25241 let candidate: String = snapshot
25242 .chars_for_range(range.clone())
25243 .skip(num_of_whitespaces)
25244 .take(ORDERED_LIST_MAX_MARKER_LEN)
25245 .collect();
25246
25247 for ordered_config in language.ordered_list() {
25248 let regex = match Regex::new(&ordered_config.pattern) {
25249 Ok(r) => r,
25250 Err(_) => continue,
25251 };
25252
25253 if let Some(captures) = regex.captures(&candidate) {
25254 let full_match = captures.get(0)?;
25255 let marker_len = full_match.len();
25256 let end_of_prefix = num_of_whitespaces + marker_len;
25257 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25258
25259 let has_content_after_marker = snapshot
25260 .chars_for_range(range)
25261 .skip(end_of_prefix)
25262 .any(|c| !c.is_whitespace());
25263
25264 if has_content_after_marker && cursor_is_after_prefix {
25265 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25266 let continuation = ordered_config
25267 .format
25268 .replace("{1}", &(number + 1).to_string());
25269 return Some(continuation.into());
25270 }
25271
25272 if start_point.column as usize == end_of_prefix {
25273 let continuation = ordered_config.format.replace("{1}", "1");
25274 if num_of_whitespaces == 0 {
25275 *newline_config = NewlineConfig::ClearCurrentLine;
25276 } else {
25277 *newline_config = NewlineConfig::UnindentCurrentLine {
25278 continuation: continuation.into(),
25279 };
25280 }
25281 }
25282
25283 return None;
25284 }
25285 }
25286
25287 None
25288}
25289
25290fn is_list_prefix_row(
25291 row: MultiBufferRow,
25292 buffer: &MultiBufferSnapshot,
25293 language: &LanguageScope,
25294) -> bool {
25295 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25296 return false;
25297 };
25298
25299 let num_of_whitespaces = snapshot
25300 .chars_for_range(range.clone())
25301 .take_while(|c| c.is_whitespace())
25302 .count();
25303
25304 let task_list_prefixes: Vec<_> = language
25305 .task_list()
25306 .into_iter()
25307 .flat_map(|config| {
25308 config
25309 .prefixes
25310 .iter()
25311 .map(|p| p.as_ref())
25312 .collect::<Vec<_>>()
25313 })
25314 .collect();
25315 let unordered_list_markers: Vec<_> = language
25316 .unordered_list()
25317 .iter()
25318 .map(|marker| marker.as_ref())
25319 .collect();
25320 let all_prefixes: Vec<_> = task_list_prefixes
25321 .into_iter()
25322 .chain(unordered_list_markers)
25323 .collect();
25324 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25325 let candidate: String = snapshot
25326 .chars_for_range(range.clone())
25327 .skip(num_of_whitespaces)
25328 .take(max_prefix_len)
25329 .collect();
25330 if all_prefixes
25331 .iter()
25332 .any(|prefix| candidate.starts_with(*prefix))
25333 {
25334 return true;
25335 }
25336 }
25337
25338 let ordered_list_candidate: String = snapshot
25339 .chars_for_range(range)
25340 .skip(num_of_whitespaces)
25341 .take(ORDERED_LIST_MAX_MARKER_LEN)
25342 .collect();
25343 for ordered_config in language.ordered_list() {
25344 let regex = match Regex::new(&ordered_config.pattern) {
25345 Ok(r) => r,
25346 Err(_) => continue,
25347 };
25348 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25349 return captures.get(0).is_some();
25350 }
25351 }
25352
25353 false
25354}
25355
25356#[derive(Debug)]
25357enum NewlineConfig {
25358 /// Insert newline with optional additional indent and optional extra blank line
25359 Newline {
25360 additional_indent: IndentSize,
25361 extra_line_additional_indent: Option<IndentSize>,
25362 prevent_auto_indent: bool,
25363 },
25364 /// Clear the current line
25365 ClearCurrentLine,
25366 /// Unindent the current line and add continuation
25367 UnindentCurrentLine { continuation: Arc<str> },
25368}
25369
25370impl NewlineConfig {
25371 fn has_extra_line(&self) -> bool {
25372 matches!(
25373 self,
25374 Self::Newline {
25375 extra_line_additional_indent: Some(_),
25376 ..
25377 }
25378 )
25379 }
25380
25381 fn insert_extra_newline_brackets(
25382 buffer: &MultiBufferSnapshot,
25383 range: Range<MultiBufferOffset>,
25384 language: &language::LanguageScope,
25385 ) -> bool {
25386 let leading_whitespace_len = buffer
25387 .reversed_chars_at(range.start)
25388 .take_while(|c| c.is_whitespace() && *c != '\n')
25389 .map(|c| c.len_utf8())
25390 .sum::<usize>();
25391 let trailing_whitespace_len = buffer
25392 .chars_at(range.end)
25393 .take_while(|c| c.is_whitespace() && *c != '\n')
25394 .map(|c| c.len_utf8())
25395 .sum::<usize>();
25396 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25397
25398 language.brackets().any(|(pair, enabled)| {
25399 let pair_start = pair.start.trim_end();
25400 let pair_end = pair.end.trim_start();
25401
25402 enabled
25403 && pair.newline
25404 && buffer.contains_str_at(range.end, pair_end)
25405 && buffer.contains_str_at(
25406 range.start.saturating_sub_usize(pair_start.len()),
25407 pair_start,
25408 )
25409 })
25410 }
25411
25412 fn insert_extra_newline_tree_sitter(
25413 buffer: &MultiBufferSnapshot,
25414 range: Range<MultiBufferOffset>,
25415 ) -> bool {
25416 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
25417 [(buffer, range, _)] => (*buffer, range.clone()),
25418 _ => return false,
25419 };
25420 let pair = {
25421 let mut result: Option<BracketMatch<usize>> = None;
25422
25423 for pair in buffer
25424 .all_bracket_ranges(range.start.0..range.end.0)
25425 .filter(move |pair| {
25426 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
25427 })
25428 {
25429 let len = pair.close_range.end - pair.open_range.start;
25430
25431 if let Some(existing) = &result {
25432 let existing_len = existing.close_range.end - existing.open_range.start;
25433 if len > existing_len {
25434 continue;
25435 }
25436 }
25437
25438 result = Some(pair);
25439 }
25440
25441 result
25442 };
25443 let Some(pair) = pair else {
25444 return false;
25445 };
25446 pair.newline_only
25447 && buffer
25448 .chars_for_range(pair.open_range.end..range.start.0)
25449 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
25450 .all(|c| c.is_whitespace() && c != '\n')
25451 }
25452}
25453
25454fn update_uncommitted_diff_for_buffer(
25455 editor: Entity<Editor>,
25456 project: &Entity<Project>,
25457 buffers: impl IntoIterator<Item = Entity<Buffer>>,
25458 buffer: Entity<MultiBuffer>,
25459 cx: &mut App,
25460) -> Task<()> {
25461 let mut tasks = Vec::new();
25462 project.update(cx, |project, cx| {
25463 for buffer in buffers {
25464 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
25465 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
25466 }
25467 }
25468 });
25469 cx.spawn(async move |cx| {
25470 let diffs = future::join_all(tasks).await;
25471 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
25472 return;
25473 }
25474
25475 buffer.update(cx, |buffer, cx| {
25476 for diff in diffs.into_iter().flatten() {
25477 buffer.add_diff(diff, cx);
25478 }
25479 });
25480 })
25481}
25482
25483fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
25484 let tab_size = tab_size.get() as usize;
25485 let mut width = offset;
25486
25487 for ch in text.chars() {
25488 width += if ch == '\t' {
25489 tab_size - (width % tab_size)
25490 } else {
25491 1
25492 };
25493 }
25494
25495 width - offset
25496}
25497
25498#[cfg(test)]
25499mod tests {
25500 use super::*;
25501
25502 #[test]
25503 fn test_string_size_with_expanded_tabs() {
25504 let nz = |val| NonZeroU32::new(val).unwrap();
25505 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
25506 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
25507 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
25508 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
25509 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
25510 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
25511 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
25512 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
25513 }
25514}
25515
25516/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
25517struct WordBreakingTokenizer<'a> {
25518 input: &'a str,
25519}
25520
25521impl<'a> WordBreakingTokenizer<'a> {
25522 fn new(input: &'a str) -> Self {
25523 Self { input }
25524 }
25525}
25526
25527fn is_char_ideographic(ch: char) -> bool {
25528 use unicode_script::Script::*;
25529 use unicode_script::UnicodeScript;
25530 matches!(ch.script(), Han | Tangut | Yi)
25531}
25532
25533fn is_grapheme_ideographic(text: &str) -> bool {
25534 text.chars().any(is_char_ideographic)
25535}
25536
25537fn is_grapheme_whitespace(text: &str) -> bool {
25538 text.chars().any(|x| x.is_whitespace())
25539}
25540
25541fn should_stay_with_preceding_ideograph(text: &str) -> bool {
25542 text.chars()
25543 .next()
25544 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
25545}
25546
25547#[derive(PartialEq, Eq, Debug, Clone, Copy)]
25548enum WordBreakToken<'a> {
25549 Word { token: &'a str, grapheme_len: usize },
25550 InlineWhitespace { token: &'a str, grapheme_len: usize },
25551 Newline,
25552}
25553
25554impl<'a> Iterator for WordBreakingTokenizer<'a> {
25555 /// Yields a span, the count of graphemes in the token, and whether it was
25556 /// whitespace. Note that it also breaks at word boundaries.
25557 type Item = WordBreakToken<'a>;
25558
25559 fn next(&mut self) -> Option<Self::Item> {
25560 use unicode_segmentation::UnicodeSegmentation;
25561 if self.input.is_empty() {
25562 return None;
25563 }
25564
25565 let mut iter = self.input.graphemes(true).peekable();
25566 let mut offset = 0;
25567 let mut grapheme_len = 0;
25568 if let Some(first_grapheme) = iter.next() {
25569 let is_newline = first_grapheme == "\n";
25570 let is_whitespace = is_grapheme_whitespace(first_grapheme);
25571 offset += first_grapheme.len();
25572 grapheme_len += 1;
25573 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
25574 if let Some(grapheme) = iter.peek().copied()
25575 && should_stay_with_preceding_ideograph(grapheme)
25576 {
25577 offset += grapheme.len();
25578 grapheme_len += 1;
25579 }
25580 } else {
25581 let mut words = self.input[offset..].split_word_bound_indices().peekable();
25582 let mut next_word_bound = words.peek().copied();
25583 if next_word_bound.is_some_and(|(i, _)| i == 0) {
25584 next_word_bound = words.next();
25585 }
25586 while let Some(grapheme) = iter.peek().copied() {
25587 if next_word_bound.is_some_and(|(i, _)| i == offset) {
25588 break;
25589 };
25590 if is_grapheme_whitespace(grapheme) != is_whitespace
25591 || (grapheme == "\n") != is_newline
25592 {
25593 break;
25594 };
25595 offset += grapheme.len();
25596 grapheme_len += 1;
25597 iter.next();
25598 }
25599 }
25600 let token = &self.input[..offset];
25601 self.input = &self.input[offset..];
25602 if token == "\n" {
25603 Some(WordBreakToken::Newline)
25604 } else if is_whitespace {
25605 Some(WordBreakToken::InlineWhitespace {
25606 token,
25607 grapheme_len,
25608 })
25609 } else {
25610 Some(WordBreakToken::Word {
25611 token,
25612 grapheme_len,
25613 })
25614 }
25615 } else {
25616 None
25617 }
25618 }
25619}
25620
25621#[test]
25622fn test_word_breaking_tokenizer() {
25623 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
25624 ("", &[]),
25625 (" ", &[whitespace(" ", 2)]),
25626 ("Ʒ", &[word("Ʒ", 1)]),
25627 ("Ǽ", &[word("Ǽ", 1)]),
25628 ("⋑", &[word("⋑", 1)]),
25629 ("⋑⋑", &[word("⋑⋑", 2)]),
25630 (
25631 "原理,进而",
25632 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
25633 ),
25634 (
25635 "hello world",
25636 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
25637 ),
25638 (
25639 "hello, world",
25640 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
25641 ),
25642 (
25643 " hello world",
25644 &[
25645 whitespace(" ", 2),
25646 word("hello", 5),
25647 whitespace(" ", 1),
25648 word("world", 5),
25649 ],
25650 ),
25651 (
25652 "这是什么 \n 钢笔",
25653 &[
25654 word("这", 1),
25655 word("是", 1),
25656 word("什", 1),
25657 word("么", 1),
25658 whitespace(" ", 1),
25659 newline(),
25660 whitespace(" ", 1),
25661 word("钢", 1),
25662 word("笔", 1),
25663 ],
25664 ),
25665 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
25666 ];
25667
25668 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
25669 WordBreakToken::Word {
25670 token,
25671 grapheme_len,
25672 }
25673 }
25674
25675 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
25676 WordBreakToken::InlineWhitespace {
25677 token,
25678 grapheme_len,
25679 }
25680 }
25681
25682 fn newline() -> WordBreakToken<'static> {
25683 WordBreakToken::Newline
25684 }
25685
25686 for (input, result) in tests {
25687 assert_eq!(
25688 WordBreakingTokenizer::new(input)
25689 .collect::<Vec<_>>()
25690 .as_slice(),
25691 *result,
25692 );
25693 }
25694}
25695
25696fn wrap_with_prefix(
25697 first_line_prefix: String,
25698 subsequent_lines_prefix: String,
25699 unwrapped_text: String,
25700 wrap_column: usize,
25701 tab_size: NonZeroU32,
25702 preserve_existing_whitespace: bool,
25703) -> String {
25704 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
25705 let subsequent_lines_prefix_len =
25706 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
25707 let mut wrapped_text = String::new();
25708 let mut current_line = first_line_prefix;
25709 let mut is_first_line = true;
25710
25711 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
25712 let mut current_line_len = first_line_prefix_len;
25713 let mut in_whitespace = false;
25714 for token in tokenizer {
25715 let have_preceding_whitespace = in_whitespace;
25716 match token {
25717 WordBreakToken::Word {
25718 token,
25719 grapheme_len,
25720 } => {
25721 in_whitespace = false;
25722 let current_prefix_len = if is_first_line {
25723 first_line_prefix_len
25724 } else {
25725 subsequent_lines_prefix_len
25726 };
25727 if current_line_len + grapheme_len > wrap_column
25728 && current_line_len != current_prefix_len
25729 {
25730 wrapped_text.push_str(current_line.trim_end());
25731 wrapped_text.push('\n');
25732 is_first_line = false;
25733 current_line = subsequent_lines_prefix.clone();
25734 current_line_len = subsequent_lines_prefix_len;
25735 }
25736 current_line.push_str(token);
25737 current_line_len += grapheme_len;
25738 }
25739 WordBreakToken::InlineWhitespace {
25740 mut token,
25741 mut grapheme_len,
25742 } => {
25743 in_whitespace = true;
25744 if have_preceding_whitespace && !preserve_existing_whitespace {
25745 continue;
25746 }
25747 if !preserve_existing_whitespace {
25748 // Keep a single whitespace grapheme as-is
25749 if let Some(first) =
25750 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
25751 {
25752 token = first;
25753 } else {
25754 token = " ";
25755 }
25756 grapheme_len = 1;
25757 }
25758 let current_prefix_len = if is_first_line {
25759 first_line_prefix_len
25760 } else {
25761 subsequent_lines_prefix_len
25762 };
25763 if current_line_len + grapheme_len > wrap_column {
25764 wrapped_text.push_str(current_line.trim_end());
25765 wrapped_text.push('\n');
25766 is_first_line = false;
25767 current_line = subsequent_lines_prefix.clone();
25768 current_line_len = subsequent_lines_prefix_len;
25769 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
25770 current_line.push_str(token);
25771 current_line_len += grapheme_len;
25772 }
25773 }
25774 WordBreakToken::Newline => {
25775 in_whitespace = true;
25776 let current_prefix_len = if is_first_line {
25777 first_line_prefix_len
25778 } else {
25779 subsequent_lines_prefix_len
25780 };
25781 if preserve_existing_whitespace {
25782 wrapped_text.push_str(current_line.trim_end());
25783 wrapped_text.push('\n');
25784 is_first_line = false;
25785 current_line = subsequent_lines_prefix.clone();
25786 current_line_len = subsequent_lines_prefix_len;
25787 } else if have_preceding_whitespace {
25788 continue;
25789 } else if current_line_len + 1 > wrap_column
25790 && current_line_len != current_prefix_len
25791 {
25792 wrapped_text.push_str(current_line.trim_end());
25793 wrapped_text.push('\n');
25794 is_first_line = false;
25795 current_line = subsequent_lines_prefix.clone();
25796 current_line_len = subsequent_lines_prefix_len;
25797 } else if current_line_len != current_prefix_len {
25798 current_line.push(' ');
25799 current_line_len += 1;
25800 }
25801 }
25802 }
25803 }
25804
25805 if !current_line.is_empty() {
25806 wrapped_text.push_str(¤t_line);
25807 }
25808 wrapped_text
25809}
25810
25811#[test]
25812fn test_wrap_with_prefix() {
25813 assert_eq!(
25814 wrap_with_prefix(
25815 "# ".to_string(),
25816 "# ".to_string(),
25817 "abcdefg".to_string(),
25818 4,
25819 NonZeroU32::new(4).unwrap(),
25820 false,
25821 ),
25822 "# abcdefg"
25823 );
25824 assert_eq!(
25825 wrap_with_prefix(
25826 "".to_string(),
25827 "".to_string(),
25828 "\thello world".to_string(),
25829 8,
25830 NonZeroU32::new(4).unwrap(),
25831 false,
25832 ),
25833 "hello\nworld"
25834 );
25835 assert_eq!(
25836 wrap_with_prefix(
25837 "// ".to_string(),
25838 "// ".to_string(),
25839 "xx \nyy zz aa bb cc".to_string(),
25840 12,
25841 NonZeroU32::new(4).unwrap(),
25842 false,
25843 ),
25844 "// xx yy zz\n// aa bb cc"
25845 );
25846 assert_eq!(
25847 wrap_with_prefix(
25848 String::new(),
25849 String::new(),
25850 "这是什么 \n 钢笔".to_string(),
25851 3,
25852 NonZeroU32::new(4).unwrap(),
25853 false,
25854 ),
25855 "这是什\n么 钢\n笔"
25856 );
25857 assert_eq!(
25858 wrap_with_prefix(
25859 String::new(),
25860 String::new(),
25861 format!("foo{}bar", '\u{2009}'), // thin space
25862 80,
25863 NonZeroU32::new(4).unwrap(),
25864 false,
25865 ),
25866 format!("foo{}bar", '\u{2009}')
25867 );
25868}
25869
25870pub trait CollaborationHub {
25871 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
25872 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
25873 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
25874}
25875
25876impl CollaborationHub for Entity<Project> {
25877 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
25878 self.read(cx).collaborators()
25879 }
25880
25881 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
25882 self.read(cx).user_store().read(cx).participant_indices()
25883 }
25884
25885 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
25886 let this = self.read(cx);
25887 let user_ids = this.collaborators().values().map(|c| c.user_id);
25888 this.user_store().read(cx).participant_names(user_ids, cx)
25889 }
25890}
25891
25892pub trait SemanticsProvider {
25893 fn hover(
25894 &self,
25895 buffer: &Entity<Buffer>,
25896 position: text::Anchor,
25897 cx: &mut App,
25898 ) -> Option<Task<Option<Vec<project::Hover>>>>;
25899
25900 fn inline_values(
25901 &self,
25902 buffer_handle: Entity<Buffer>,
25903 range: Range<text::Anchor>,
25904 cx: &mut App,
25905 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
25906
25907 fn applicable_inlay_chunks(
25908 &self,
25909 buffer: &Entity<Buffer>,
25910 ranges: &[Range<text::Anchor>],
25911 cx: &mut App,
25912 ) -> Vec<Range<BufferRow>>;
25913
25914 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
25915
25916 fn inlay_hints(
25917 &self,
25918 invalidate: InvalidationStrategy,
25919 buffer: Entity<Buffer>,
25920 ranges: Vec<Range<text::Anchor>>,
25921 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
25922 cx: &mut App,
25923 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
25924
25925 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
25926
25927 fn document_highlights(
25928 &self,
25929 buffer: &Entity<Buffer>,
25930 position: text::Anchor,
25931 cx: &mut App,
25932 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
25933
25934 fn definitions(
25935 &self,
25936 buffer: &Entity<Buffer>,
25937 position: text::Anchor,
25938 kind: GotoDefinitionKind,
25939 cx: &mut App,
25940 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
25941
25942 fn range_for_rename(
25943 &self,
25944 buffer: &Entity<Buffer>,
25945 position: text::Anchor,
25946 cx: &mut App,
25947 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
25948
25949 fn perform_rename(
25950 &self,
25951 buffer: &Entity<Buffer>,
25952 position: text::Anchor,
25953 new_name: String,
25954 cx: &mut App,
25955 ) -> Option<Task<Result<ProjectTransaction>>>;
25956}
25957
25958pub trait CompletionProvider {
25959 fn completions(
25960 &self,
25961 excerpt_id: ExcerptId,
25962 buffer: &Entity<Buffer>,
25963 buffer_position: text::Anchor,
25964 trigger: CompletionContext,
25965 window: &mut Window,
25966 cx: &mut Context<Editor>,
25967 ) -> Task<Result<Vec<CompletionResponse>>>;
25968
25969 fn resolve_completions(
25970 &self,
25971 _buffer: Entity<Buffer>,
25972 _completion_indices: Vec<usize>,
25973 _completions: Rc<RefCell<Box<[Completion]>>>,
25974 _cx: &mut Context<Editor>,
25975 ) -> Task<Result<bool>> {
25976 Task::ready(Ok(false))
25977 }
25978
25979 fn apply_additional_edits_for_completion(
25980 &self,
25981 _buffer: Entity<Buffer>,
25982 _completions: Rc<RefCell<Box<[Completion]>>>,
25983 _completion_index: usize,
25984 _push_to_history: bool,
25985 _cx: &mut Context<Editor>,
25986 ) -> Task<Result<Option<language::Transaction>>> {
25987 Task::ready(Ok(None))
25988 }
25989
25990 fn is_completion_trigger(
25991 &self,
25992 buffer: &Entity<Buffer>,
25993 position: language::Anchor,
25994 text: &str,
25995 trigger_in_words: bool,
25996 cx: &mut Context<Editor>,
25997 ) -> bool;
25998
25999 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26000
26001 fn sort_completions(&self) -> bool {
26002 true
26003 }
26004
26005 fn filter_completions(&self) -> bool {
26006 true
26007 }
26008
26009 fn show_snippets(&self) -> bool {
26010 false
26011 }
26012}
26013
26014pub trait CodeActionProvider {
26015 fn id(&self) -> Arc<str>;
26016
26017 fn code_actions(
26018 &self,
26019 buffer: &Entity<Buffer>,
26020 range: Range<text::Anchor>,
26021 window: &mut Window,
26022 cx: &mut App,
26023 ) -> Task<Result<Vec<CodeAction>>>;
26024
26025 fn apply_code_action(
26026 &self,
26027 buffer_handle: Entity<Buffer>,
26028 action: CodeAction,
26029 excerpt_id: ExcerptId,
26030 push_to_history: bool,
26031 window: &mut Window,
26032 cx: &mut App,
26033 ) -> Task<Result<ProjectTransaction>>;
26034}
26035
26036impl CodeActionProvider for Entity<Project> {
26037 fn id(&self) -> Arc<str> {
26038 "project".into()
26039 }
26040
26041 fn code_actions(
26042 &self,
26043 buffer: &Entity<Buffer>,
26044 range: Range<text::Anchor>,
26045 _window: &mut Window,
26046 cx: &mut App,
26047 ) -> Task<Result<Vec<CodeAction>>> {
26048 self.update(cx, |project, cx| {
26049 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26050 let code_actions = project.code_actions(buffer, range, None, cx);
26051 cx.background_spawn(async move {
26052 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26053 Ok(code_lens_actions
26054 .context("code lens fetch")?
26055 .into_iter()
26056 .flatten()
26057 .chain(
26058 code_actions
26059 .context("code action fetch")?
26060 .into_iter()
26061 .flatten(),
26062 )
26063 .collect())
26064 })
26065 })
26066 }
26067
26068 fn apply_code_action(
26069 &self,
26070 buffer_handle: Entity<Buffer>,
26071 action: CodeAction,
26072 _excerpt_id: ExcerptId,
26073 push_to_history: bool,
26074 _window: &mut Window,
26075 cx: &mut App,
26076 ) -> Task<Result<ProjectTransaction>> {
26077 self.update(cx, |project, cx| {
26078 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26079 })
26080 }
26081}
26082
26083fn snippet_completions(
26084 project: &Project,
26085 buffer: &Entity<Buffer>,
26086 buffer_anchor: text::Anchor,
26087 classifier: CharClassifier,
26088 cx: &mut App,
26089) -> Task<Result<CompletionResponse>> {
26090 let languages = buffer.read(cx).languages_at(buffer_anchor);
26091 let snippet_store = project.snippets().read(cx);
26092
26093 let scopes: Vec<_> = languages
26094 .iter()
26095 .filter_map(|language| {
26096 let language_name = language.lsp_id();
26097 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26098
26099 if snippets.is_empty() {
26100 None
26101 } else {
26102 Some((language.default_scope(), snippets))
26103 }
26104 })
26105 .collect();
26106
26107 if scopes.is_empty() {
26108 return Task::ready(Ok(CompletionResponse {
26109 completions: vec![],
26110 display_options: CompletionDisplayOptions::default(),
26111 is_incomplete: false,
26112 }));
26113 }
26114
26115 let snapshot = buffer.read(cx).text_snapshot();
26116 let executor = cx.background_executor().clone();
26117
26118 cx.background_spawn(async move {
26119 let is_word_char = |c| classifier.is_word(c);
26120
26121 let mut is_incomplete = false;
26122 let mut completions: Vec<Completion> = Vec::new();
26123
26124 const MAX_PREFIX_LEN: usize = 128;
26125 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26126 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26127 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26128
26129 let max_buffer_window: String = snapshot
26130 .text_for_range(window_start..buffer_offset)
26131 .collect();
26132
26133 if max_buffer_window.is_empty() {
26134 return Ok(CompletionResponse {
26135 completions: vec![],
26136 display_options: CompletionDisplayOptions::default(),
26137 is_incomplete: true,
26138 });
26139 }
26140
26141 for (_scope, snippets) in scopes.into_iter() {
26142 // Sort snippets by word count to match longer snippet prefixes first.
26143 let mut sorted_snippet_candidates = snippets
26144 .iter()
26145 .enumerate()
26146 .flat_map(|(snippet_ix, snippet)| {
26147 snippet
26148 .prefix
26149 .iter()
26150 .enumerate()
26151 .map(move |(prefix_ix, prefix)| {
26152 let word_count =
26153 snippet_candidate_suffixes(prefix, is_word_char).count();
26154 ((snippet_ix, prefix_ix), prefix, word_count)
26155 })
26156 })
26157 .collect_vec();
26158 sorted_snippet_candidates
26159 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26160
26161 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26162
26163 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26164 .take(
26165 sorted_snippet_candidates
26166 .first()
26167 .map(|(_, _, word_count)| *word_count)
26168 .unwrap_or_default(),
26169 )
26170 .collect_vec();
26171
26172 const MAX_RESULTS: usize = 100;
26173 // Each match also remembers how many characters from the buffer it consumed
26174 let mut matches: Vec<(StringMatch, usize)> = vec![];
26175
26176 let mut snippet_list_cutoff_index = 0;
26177 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26178 let word_count = buffer_index + 1;
26179 // Increase `snippet_list_cutoff_index` until we have all of the
26180 // snippets with sufficiently many words.
26181 while sorted_snippet_candidates
26182 .get(snippet_list_cutoff_index)
26183 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26184 *snippet_word_count >= word_count
26185 })
26186 {
26187 snippet_list_cutoff_index += 1;
26188 }
26189
26190 // Take only the candidates with at least `word_count` many words
26191 let snippet_candidates_at_word_len =
26192 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26193
26194 let candidates = snippet_candidates_at_word_len
26195 .iter()
26196 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26197 .enumerate() // index in `sorted_snippet_candidates`
26198 // First char must match
26199 .filter(|(_ix, prefix)| {
26200 itertools::equal(
26201 prefix
26202 .chars()
26203 .next()
26204 .into_iter()
26205 .flat_map(|c| c.to_lowercase()),
26206 buffer_window
26207 .chars()
26208 .next()
26209 .into_iter()
26210 .flat_map(|c| c.to_lowercase()),
26211 )
26212 })
26213 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26214 .collect::<Vec<StringMatchCandidate>>();
26215
26216 matches.extend(
26217 fuzzy::match_strings(
26218 &candidates,
26219 &buffer_window,
26220 buffer_window.chars().any(|c| c.is_uppercase()),
26221 true,
26222 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26223 &Default::default(),
26224 executor.clone(),
26225 )
26226 .await
26227 .into_iter()
26228 .map(|string_match| (string_match, buffer_window.len())),
26229 );
26230
26231 if matches.len() >= MAX_RESULTS {
26232 break;
26233 }
26234 }
26235
26236 let to_lsp = |point: &text::Anchor| {
26237 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26238 point_to_lsp(end)
26239 };
26240 let lsp_end = to_lsp(&buffer_anchor);
26241
26242 if matches.len() >= MAX_RESULTS {
26243 is_incomplete = true;
26244 }
26245
26246 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26247 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26248 sorted_snippet_candidates[string_match.candidate_id];
26249 let snippet = &snippets[snippet_index];
26250 let start = buffer_offset - buffer_window_len;
26251 let start = snapshot.anchor_before(start);
26252 let range = start..buffer_anchor;
26253 let lsp_start = to_lsp(&start);
26254 let lsp_range = lsp::Range {
26255 start: lsp_start,
26256 end: lsp_end,
26257 };
26258 Completion {
26259 replace_range: range,
26260 new_text: snippet.body.clone(),
26261 source: CompletionSource::Lsp {
26262 insert_range: None,
26263 server_id: LanguageServerId(usize::MAX),
26264 resolved: true,
26265 lsp_completion: Box::new(lsp::CompletionItem {
26266 label: snippet.prefix.first().unwrap().clone(),
26267 kind: Some(CompletionItemKind::SNIPPET),
26268 label_details: snippet.description.as_ref().map(|description| {
26269 lsp::CompletionItemLabelDetails {
26270 detail: Some(description.clone()),
26271 description: None,
26272 }
26273 }),
26274 insert_text_format: Some(InsertTextFormat::SNIPPET),
26275 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26276 lsp::InsertReplaceEdit {
26277 new_text: snippet.body.clone(),
26278 insert: lsp_range,
26279 replace: lsp_range,
26280 },
26281 )),
26282 filter_text: Some(snippet.body.clone()),
26283 sort_text: Some(char::MAX.to_string()),
26284 ..lsp::CompletionItem::default()
26285 }),
26286 lsp_defaults: None,
26287 },
26288 label: CodeLabel {
26289 text: matching_prefix.clone(),
26290 runs: Vec::new(),
26291 filter_range: 0..matching_prefix.len(),
26292 },
26293 icon_path: None,
26294 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26295 single_line: snippet.name.clone().into(),
26296 plain_text: snippet
26297 .description
26298 .clone()
26299 .map(|description| description.into()),
26300 }),
26301 insert_text_mode: None,
26302 confirm: None,
26303 match_start: Some(start),
26304 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26305 }
26306 }));
26307 }
26308
26309 Ok(CompletionResponse {
26310 completions,
26311 display_options: CompletionDisplayOptions::default(),
26312 is_incomplete,
26313 })
26314 })
26315}
26316
26317impl CompletionProvider for Entity<Project> {
26318 fn completions(
26319 &self,
26320 _excerpt_id: ExcerptId,
26321 buffer: &Entity<Buffer>,
26322 buffer_position: text::Anchor,
26323 options: CompletionContext,
26324 _window: &mut Window,
26325 cx: &mut Context<Editor>,
26326 ) -> Task<Result<Vec<CompletionResponse>>> {
26327 self.update(cx, |project, cx| {
26328 let task = project.completions(buffer, buffer_position, options, cx);
26329 cx.background_spawn(task)
26330 })
26331 }
26332
26333 fn resolve_completions(
26334 &self,
26335 buffer: Entity<Buffer>,
26336 completion_indices: Vec<usize>,
26337 completions: Rc<RefCell<Box<[Completion]>>>,
26338 cx: &mut Context<Editor>,
26339 ) -> Task<Result<bool>> {
26340 self.update(cx, |project, cx| {
26341 project.lsp_store().update(cx, |lsp_store, cx| {
26342 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26343 })
26344 })
26345 }
26346
26347 fn apply_additional_edits_for_completion(
26348 &self,
26349 buffer: Entity<Buffer>,
26350 completions: Rc<RefCell<Box<[Completion]>>>,
26351 completion_index: usize,
26352 push_to_history: bool,
26353 cx: &mut Context<Editor>,
26354 ) -> Task<Result<Option<language::Transaction>>> {
26355 self.update(cx, |project, cx| {
26356 project.lsp_store().update(cx, |lsp_store, cx| {
26357 lsp_store.apply_additional_edits_for_completion(
26358 buffer,
26359 completions,
26360 completion_index,
26361 push_to_history,
26362 cx,
26363 )
26364 })
26365 })
26366 }
26367
26368 fn is_completion_trigger(
26369 &self,
26370 buffer: &Entity<Buffer>,
26371 position: language::Anchor,
26372 text: &str,
26373 trigger_in_words: bool,
26374 cx: &mut Context<Editor>,
26375 ) -> bool {
26376 let mut chars = text.chars();
26377 let char = if let Some(char) = chars.next() {
26378 char
26379 } else {
26380 return false;
26381 };
26382 if chars.next().is_some() {
26383 return false;
26384 }
26385
26386 let buffer = buffer.read(cx);
26387 let snapshot = buffer.snapshot();
26388 let classifier = snapshot
26389 .char_classifier_at(position)
26390 .scope_context(Some(CharScopeContext::Completion));
26391 if trigger_in_words && classifier.is_word(char) {
26392 return true;
26393 }
26394
26395 buffer.completion_triggers().contains(text)
26396 }
26397
26398 fn show_snippets(&self) -> bool {
26399 true
26400 }
26401}
26402
26403impl SemanticsProvider for Entity<Project> {
26404 fn hover(
26405 &self,
26406 buffer: &Entity<Buffer>,
26407 position: text::Anchor,
26408 cx: &mut App,
26409 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26410 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
26411 }
26412
26413 fn document_highlights(
26414 &self,
26415 buffer: &Entity<Buffer>,
26416 position: text::Anchor,
26417 cx: &mut App,
26418 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
26419 Some(self.update(cx, |project, cx| {
26420 project.document_highlights(buffer, position, cx)
26421 }))
26422 }
26423
26424 fn definitions(
26425 &self,
26426 buffer: &Entity<Buffer>,
26427 position: text::Anchor,
26428 kind: GotoDefinitionKind,
26429 cx: &mut App,
26430 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
26431 Some(self.update(cx, |project, cx| match kind {
26432 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
26433 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
26434 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
26435 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
26436 }))
26437 }
26438
26439 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
26440 self.update(cx, |project, cx| {
26441 if project
26442 .active_debug_session(cx)
26443 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
26444 {
26445 return true;
26446 }
26447
26448 buffer.update(cx, |buffer, cx| {
26449 project.any_language_server_supports_inlay_hints(buffer, cx)
26450 })
26451 })
26452 }
26453
26454 fn inline_values(
26455 &self,
26456 buffer_handle: Entity<Buffer>,
26457 range: Range<text::Anchor>,
26458 cx: &mut App,
26459 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
26460 self.update(cx, |project, cx| {
26461 let (session, active_stack_frame) = project.active_debug_session(cx)?;
26462
26463 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
26464 })
26465 }
26466
26467 fn applicable_inlay_chunks(
26468 &self,
26469 buffer: &Entity<Buffer>,
26470 ranges: &[Range<text::Anchor>],
26471 cx: &mut App,
26472 ) -> Vec<Range<BufferRow>> {
26473 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26474 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
26475 })
26476 }
26477
26478 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
26479 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
26480 lsp_store.invalidate_inlay_hints(for_buffers)
26481 });
26482 }
26483
26484 fn inlay_hints(
26485 &self,
26486 invalidate: InvalidationStrategy,
26487 buffer: Entity<Buffer>,
26488 ranges: Vec<Range<text::Anchor>>,
26489 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26490 cx: &mut App,
26491 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
26492 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26493 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
26494 }))
26495 }
26496
26497 fn range_for_rename(
26498 &self,
26499 buffer: &Entity<Buffer>,
26500 position: text::Anchor,
26501 cx: &mut App,
26502 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
26503 Some(self.update(cx, |project, cx| {
26504 let buffer = buffer.clone();
26505 let task = project.prepare_rename(buffer.clone(), position, cx);
26506 cx.spawn(async move |_, cx| {
26507 Ok(match task.await? {
26508 PrepareRenameResponse::Success(range) => Some(range),
26509 PrepareRenameResponse::InvalidPosition => None,
26510 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
26511 // Fallback on using TreeSitter info to determine identifier range
26512 buffer.read_with(cx, |buffer, _| {
26513 let snapshot = buffer.snapshot();
26514 let (range, kind) = snapshot.surrounding_word(position, None);
26515 if kind != Some(CharKind::Word) {
26516 return None;
26517 }
26518 Some(
26519 snapshot.anchor_before(range.start)
26520 ..snapshot.anchor_after(range.end),
26521 )
26522 })
26523 }
26524 })
26525 })
26526 }))
26527 }
26528
26529 fn perform_rename(
26530 &self,
26531 buffer: &Entity<Buffer>,
26532 position: text::Anchor,
26533 new_name: String,
26534 cx: &mut App,
26535 ) -> Option<Task<Result<ProjectTransaction>>> {
26536 Some(self.update(cx, |project, cx| {
26537 project.perform_rename(buffer.clone(), position, new_name, cx)
26538 }))
26539 }
26540}
26541
26542fn consume_contiguous_rows(
26543 contiguous_row_selections: &mut Vec<Selection<Point>>,
26544 selection: &Selection<Point>,
26545 display_map: &DisplaySnapshot,
26546 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
26547) -> (MultiBufferRow, MultiBufferRow) {
26548 contiguous_row_selections.push(selection.clone());
26549 let start_row = starting_row(selection, display_map);
26550 let mut end_row = ending_row(selection, display_map);
26551
26552 while let Some(next_selection) = selections.peek() {
26553 if next_selection.start.row <= end_row.0 {
26554 end_row = ending_row(next_selection, display_map);
26555 contiguous_row_selections.push(selections.next().unwrap().clone());
26556 } else {
26557 break;
26558 }
26559 }
26560 (start_row, end_row)
26561}
26562
26563fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26564 if selection.start.column > 0 {
26565 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
26566 } else {
26567 MultiBufferRow(selection.start.row)
26568 }
26569}
26570
26571fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26572 if next_selection.end.column > 0 || next_selection.is_empty() {
26573 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
26574 } else {
26575 MultiBufferRow(next_selection.end.row)
26576 }
26577}
26578
26579impl EditorSnapshot {
26580 pub fn remote_selections_in_range<'a>(
26581 &'a self,
26582 range: &'a Range<Anchor>,
26583 collaboration_hub: &dyn CollaborationHub,
26584 cx: &'a App,
26585 ) -> impl 'a + Iterator<Item = RemoteSelection> {
26586 let participant_names = collaboration_hub.user_names(cx);
26587 let participant_indices = collaboration_hub.user_participant_indices(cx);
26588 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
26589 let collaborators_by_replica_id = collaborators_by_peer_id
26590 .values()
26591 .map(|collaborator| (collaborator.replica_id, collaborator))
26592 .collect::<HashMap<_, _>>();
26593 self.buffer_snapshot()
26594 .selections_in_range(range, false)
26595 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
26596 if replica_id == ReplicaId::AGENT {
26597 Some(RemoteSelection {
26598 replica_id,
26599 selection,
26600 cursor_shape,
26601 line_mode,
26602 collaborator_id: CollaboratorId::Agent,
26603 user_name: Some("Agent".into()),
26604 color: cx.theme().players().agent(),
26605 })
26606 } else {
26607 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
26608 let participant_index = participant_indices.get(&collaborator.user_id).copied();
26609 let user_name = participant_names.get(&collaborator.user_id).cloned();
26610 Some(RemoteSelection {
26611 replica_id,
26612 selection,
26613 cursor_shape,
26614 line_mode,
26615 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
26616 user_name,
26617 color: if let Some(index) = participant_index {
26618 cx.theme().players().color_for_participant(index.0)
26619 } else {
26620 cx.theme().players().absent()
26621 },
26622 })
26623 }
26624 })
26625 }
26626
26627 pub fn hunks_for_ranges(
26628 &self,
26629 ranges: impl IntoIterator<Item = Range<Point>>,
26630 ) -> Vec<MultiBufferDiffHunk> {
26631 let mut hunks = Vec::new();
26632 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
26633 HashMap::default();
26634 for query_range in ranges {
26635 let query_rows =
26636 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
26637 for hunk in self.buffer_snapshot().diff_hunks_in_range(
26638 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
26639 ) {
26640 // Include deleted hunks that are adjacent to the query range, because
26641 // otherwise they would be missed.
26642 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
26643 if hunk.status().is_deleted() {
26644 intersects_range |= hunk.row_range.start == query_rows.end;
26645 intersects_range |= hunk.row_range.end == query_rows.start;
26646 }
26647 if intersects_range {
26648 if !processed_buffer_rows
26649 .entry(hunk.buffer_id)
26650 .or_default()
26651 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
26652 {
26653 continue;
26654 }
26655 hunks.push(hunk);
26656 }
26657 }
26658 }
26659
26660 hunks
26661 }
26662
26663 fn display_diff_hunks_for_rows<'a>(
26664 &'a self,
26665 display_rows: Range<DisplayRow>,
26666 folded_buffers: &'a HashSet<BufferId>,
26667 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
26668 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
26669 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
26670
26671 self.buffer_snapshot()
26672 .diff_hunks_in_range(buffer_start..buffer_end)
26673 .filter_map(|hunk| {
26674 if folded_buffers.contains(&hunk.buffer_id) {
26675 return None;
26676 }
26677
26678 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
26679 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
26680
26681 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
26682 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
26683
26684 let display_hunk = if hunk_display_start.column() != 0 {
26685 DisplayDiffHunk::Folded {
26686 display_row: hunk_display_start.row(),
26687 }
26688 } else {
26689 let mut end_row = hunk_display_end.row();
26690 if hunk_display_end.column() > 0 {
26691 end_row.0 += 1;
26692 }
26693 let is_created_file = hunk.is_created_file();
26694
26695 DisplayDiffHunk::Unfolded {
26696 status: hunk.status(),
26697 diff_base_byte_range: hunk.diff_base_byte_range.start.0
26698 ..hunk.diff_base_byte_range.end.0,
26699 word_diffs: hunk.word_diffs,
26700 display_row_range: hunk_display_start.row()..end_row,
26701 multi_buffer_range: Anchor::range_in_buffer(
26702 hunk.excerpt_id,
26703 hunk.buffer_range,
26704 ),
26705 is_created_file,
26706 }
26707 };
26708
26709 Some(display_hunk)
26710 })
26711 }
26712
26713 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
26714 self.display_snapshot
26715 .buffer_snapshot()
26716 .language_at(position)
26717 }
26718
26719 pub fn is_focused(&self) -> bool {
26720 self.is_focused
26721 }
26722
26723 pub fn placeholder_text(&self) -> Option<String> {
26724 self.placeholder_display_snapshot
26725 .as_ref()
26726 .map(|display_map| display_map.text())
26727 }
26728
26729 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
26730 self.scroll_anchor.scroll_position(&self.display_snapshot)
26731 }
26732
26733 pub fn gutter_dimensions(
26734 &self,
26735 font_id: FontId,
26736 font_size: Pixels,
26737 style: &EditorStyle,
26738 window: &mut Window,
26739 cx: &App,
26740 ) -> GutterDimensions {
26741 if self.show_gutter
26742 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
26743 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
26744 {
26745 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
26746 matches!(
26747 ProjectSettings::get_global(cx).git.git_gutter,
26748 GitGutterSetting::TrackedFiles
26749 )
26750 });
26751 let gutter_settings = EditorSettings::get_global(cx).gutter;
26752 let show_line_numbers = self
26753 .show_line_numbers
26754 .unwrap_or(gutter_settings.line_numbers);
26755 let line_gutter_width = if show_line_numbers {
26756 // Avoid flicker-like gutter resizes when the line number gains another digit by
26757 // only resizing the gutter on files with > 10**min_line_number_digits lines.
26758 let min_width_for_number_on_gutter =
26759 ch_advance * gutter_settings.min_line_number_digits as f32;
26760 self.max_line_number_width(style, window)
26761 .max(min_width_for_number_on_gutter)
26762 } else {
26763 0.0.into()
26764 };
26765
26766 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
26767 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
26768
26769 let git_blame_entries_width =
26770 self.git_blame_gutter_max_author_length
26771 .map(|max_author_length| {
26772 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
26773 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
26774
26775 /// The number of characters to dedicate to gaps and margins.
26776 const SPACING_WIDTH: usize = 4;
26777
26778 let max_char_count = max_author_length.min(renderer.max_author_length())
26779 + ::git::SHORT_SHA_LENGTH
26780 + MAX_RELATIVE_TIMESTAMP.len()
26781 + SPACING_WIDTH;
26782
26783 ch_advance * max_char_count
26784 });
26785
26786 let is_singleton = self.buffer_snapshot().is_singleton();
26787
26788 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
26789 left_padding += if !is_singleton {
26790 ch_width * 4.0
26791 } else if show_runnables || show_breakpoints {
26792 ch_width * 3.0
26793 } else if show_git_gutter && show_line_numbers {
26794 ch_width * 2.0
26795 } else if show_git_gutter || show_line_numbers {
26796 ch_width
26797 } else {
26798 px(0.)
26799 };
26800
26801 let shows_folds = is_singleton && gutter_settings.folds;
26802
26803 let right_padding = if shows_folds && show_line_numbers {
26804 ch_width * 4.0
26805 } else if shows_folds || (!is_singleton && show_line_numbers) {
26806 ch_width * 3.0
26807 } else if show_line_numbers {
26808 ch_width
26809 } else {
26810 px(0.)
26811 };
26812
26813 GutterDimensions {
26814 left_padding,
26815 right_padding,
26816 width: line_gutter_width + left_padding + right_padding,
26817 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
26818 git_blame_entries_width,
26819 }
26820 } else if self.offset_content {
26821 GutterDimensions::default_with_margin(font_id, font_size, cx)
26822 } else {
26823 GutterDimensions::default()
26824 }
26825 }
26826
26827 pub fn render_crease_toggle(
26828 &self,
26829 buffer_row: MultiBufferRow,
26830 row_contains_cursor: bool,
26831 editor: Entity<Editor>,
26832 window: &mut Window,
26833 cx: &mut App,
26834 ) -> Option<AnyElement> {
26835 let folded = self.is_line_folded(buffer_row);
26836 let mut is_foldable = false;
26837
26838 if let Some(crease) = self
26839 .crease_snapshot
26840 .query_row(buffer_row, self.buffer_snapshot())
26841 {
26842 is_foldable = true;
26843 match crease {
26844 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
26845 if let Some(render_toggle) = render_toggle {
26846 let toggle_callback =
26847 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
26848 if folded {
26849 editor.update(cx, |editor, cx| {
26850 editor.fold_at(buffer_row, window, cx)
26851 });
26852 } else {
26853 editor.update(cx, |editor, cx| {
26854 editor.unfold_at(buffer_row, window, cx)
26855 });
26856 }
26857 });
26858 return Some((render_toggle)(
26859 buffer_row,
26860 folded,
26861 toggle_callback,
26862 window,
26863 cx,
26864 ));
26865 }
26866 }
26867 }
26868 }
26869
26870 is_foldable |= self.starts_indent(buffer_row);
26871
26872 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
26873 Some(
26874 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
26875 .toggle_state(folded)
26876 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
26877 if folded {
26878 this.unfold_at(buffer_row, window, cx);
26879 } else {
26880 this.fold_at(buffer_row, window, cx);
26881 }
26882 }))
26883 .into_any_element(),
26884 )
26885 } else {
26886 None
26887 }
26888 }
26889
26890 pub fn render_crease_trailer(
26891 &self,
26892 buffer_row: MultiBufferRow,
26893 window: &mut Window,
26894 cx: &mut App,
26895 ) -> Option<AnyElement> {
26896 let folded = self.is_line_folded(buffer_row);
26897 if let Crease::Inline { render_trailer, .. } = self
26898 .crease_snapshot
26899 .query_row(buffer_row, self.buffer_snapshot())?
26900 {
26901 let render_trailer = render_trailer.as_ref()?;
26902 Some(render_trailer(buffer_row, folded, window, cx))
26903 } else {
26904 None
26905 }
26906 }
26907
26908 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
26909 let digit_count = self.widest_line_number().ilog10() + 1;
26910 column_pixels(style, digit_count as usize, window)
26911 }
26912
26913 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
26914 ///
26915 /// This is positive if `base` is before `line`.
26916 fn relative_line_delta(
26917 &self,
26918 current_selection_head: DisplayRow,
26919 first_visible_row: DisplayRow,
26920 consider_wrapped_lines: bool,
26921 ) -> i64 {
26922 let current_selection_head = current_selection_head.as_display_point().to_point(self);
26923 let first_visible_row = first_visible_row.as_display_point().to_point(self);
26924
26925 if consider_wrapped_lines {
26926 let wrap_snapshot = self.wrap_snapshot();
26927 let base_wrap_row = wrap_snapshot
26928 .make_wrap_point(current_selection_head, Bias::Left)
26929 .row();
26930 let wrap_row = wrap_snapshot
26931 .make_wrap_point(first_visible_row, Bias::Left)
26932 .row();
26933 wrap_row.0 as i64 - base_wrap_row.0 as i64
26934 } else {
26935 let folds = if current_selection_head < first_visible_row {
26936 self.folds_in_range(current_selection_head..first_visible_row)
26937 } else {
26938 self.folds_in_range(first_visible_row..current_selection_head)
26939 };
26940
26941 let folded_lines = folds
26942 .map(|fold| {
26943 let range = fold.range.0.to_point(self);
26944 range.end.row.saturating_sub(range.start.row)
26945 })
26946 .sum::<u32>() as i64;
26947
26948 first_visible_row.row as i64 - current_selection_head.row as i64 + folded_lines
26949 }
26950 }
26951
26952 /// Returns the unsigned relative line number to display for each row in `rows`.
26953 ///
26954 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
26955 pub fn calculate_relative_line_numbers(
26956 &self,
26957 rows: &Range<DisplayRow>,
26958 current_selection_head: DisplayRow,
26959 count_wrapped_lines: bool,
26960 ) -> HashMap<DisplayRow, u32> {
26961 let initial_offset =
26962 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
26963 let current_selection_point = current_selection_head.as_display_point().to_point(self);
26964
26965 self.row_infos(rows.start)
26966 .take(rows.len())
26967 .enumerate()
26968 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
26969 .filter(|(_row, row_info)| {
26970 row_info.buffer_row.is_some()
26971 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
26972 })
26973 .enumerate()
26974 .filter(|(_, (row, row_info))| {
26975 // We want to check here that
26976 // - the row is not the current selection head to ensure the current
26977 // line has absolute numbering
26978 // - similarly, should the selection head live in a soft-wrapped line
26979 // and we are not counting those, that the parent line keeps its
26980 // absolute number
26981 // - lastly, if we are in a deleted line, it is fine to number this
26982 // relative with 0, as otherwise it would have no line number at all
26983 (*row != current_selection_head
26984 && (count_wrapped_lines
26985 || row_info.buffer_row != Some(current_selection_point.row)))
26986 || row_info
26987 .diff_status
26988 .is_some_and(|status| status.is_deleted())
26989 })
26990 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
26991 .collect()
26992 }
26993}
26994
26995pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
26996 let font_size = style.text.font_size.to_pixels(window.rem_size());
26997 let layout = window.text_system().shape_line(
26998 SharedString::from(" ".repeat(column)),
26999 font_size,
27000 &[TextRun {
27001 len: column,
27002 font: style.text.font(),
27003 color: Hsla::default(),
27004 ..Default::default()
27005 }],
27006 None,
27007 );
27008
27009 layout.width
27010}
27011
27012impl Deref for EditorSnapshot {
27013 type Target = DisplaySnapshot;
27014
27015 fn deref(&self) -> &Self::Target {
27016 &self.display_snapshot
27017 }
27018}
27019
27020#[derive(Clone, Debug, PartialEq, Eq)]
27021pub enum EditorEvent {
27022 /// Emitted when the stored review comments change (added, removed, or updated).
27023 ReviewCommentsChanged {
27024 /// The new total count of review comments.
27025 total_count: usize,
27026 },
27027 InputIgnored {
27028 text: Arc<str>,
27029 },
27030 InputHandled {
27031 utf16_range_to_replace: Option<Range<isize>>,
27032 text: Arc<str>,
27033 },
27034 ExcerptsAdded {
27035 buffer: Entity<Buffer>,
27036 predecessor: ExcerptId,
27037 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27038 },
27039 ExcerptsRemoved {
27040 ids: Vec<ExcerptId>,
27041 removed_buffer_ids: Vec<BufferId>,
27042 },
27043 BufferFoldToggled {
27044 ids: Vec<ExcerptId>,
27045 folded: bool,
27046 },
27047 ExcerptsEdited {
27048 ids: Vec<ExcerptId>,
27049 },
27050 ExcerptsExpanded {
27051 ids: Vec<ExcerptId>,
27052 },
27053 ExpandExcerptsRequested {
27054 excerpt_ids: Vec<ExcerptId>,
27055 lines: u32,
27056 direction: ExpandExcerptDirection,
27057 },
27058 BufferEdited,
27059 Edited {
27060 transaction_id: clock::Lamport,
27061 },
27062 Reparsed(BufferId),
27063 Focused,
27064 FocusedIn,
27065 Blurred,
27066 DirtyChanged,
27067 Saved,
27068 TitleChanged,
27069 SelectionsChanged {
27070 local: bool,
27071 },
27072 ScrollPositionChanged {
27073 local: bool,
27074 autoscroll: bool,
27075 },
27076 TransactionUndone {
27077 transaction_id: clock::Lamport,
27078 },
27079 TransactionBegun {
27080 transaction_id: clock::Lamport,
27081 },
27082 CursorShapeChanged,
27083 BreadcrumbsChanged,
27084 PushedToNavHistory {
27085 anchor: Anchor,
27086 is_deactivate: bool,
27087 },
27088}
27089
27090impl EventEmitter<EditorEvent> for Editor {}
27091
27092impl Focusable for Editor {
27093 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27094 self.focus_handle.clone()
27095 }
27096}
27097
27098impl Render for Editor {
27099 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27100 EditorElement::new(&cx.entity(), self.create_style(cx))
27101 }
27102}
27103
27104impl EntityInputHandler for Editor {
27105 fn text_for_range(
27106 &mut self,
27107 range_utf16: Range<usize>,
27108 adjusted_range: &mut Option<Range<usize>>,
27109 _: &mut Window,
27110 cx: &mut Context<Self>,
27111 ) -> Option<String> {
27112 let snapshot = self.buffer.read(cx).read(cx);
27113 let start = snapshot.clip_offset_utf16(
27114 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27115 Bias::Left,
27116 );
27117 let end = snapshot.clip_offset_utf16(
27118 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27119 Bias::Right,
27120 );
27121 if (start.0.0..end.0.0) != range_utf16 {
27122 adjusted_range.replace(start.0.0..end.0.0);
27123 }
27124 Some(snapshot.text_for_range(start..end).collect())
27125 }
27126
27127 fn selected_text_range(
27128 &mut self,
27129 ignore_disabled_input: bool,
27130 _: &mut Window,
27131 cx: &mut Context<Self>,
27132 ) -> Option<UTF16Selection> {
27133 // Prevent the IME menu from appearing when holding down an alphabetic key
27134 // while input is disabled.
27135 if !ignore_disabled_input && !self.input_enabled {
27136 return None;
27137 }
27138
27139 let selection = self
27140 .selections
27141 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27142 let range = selection.range();
27143
27144 Some(UTF16Selection {
27145 range: range.start.0.0..range.end.0.0,
27146 reversed: selection.reversed,
27147 })
27148 }
27149
27150 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27151 let snapshot = self.buffer.read(cx).read(cx);
27152 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
27153 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27154 }
27155
27156 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27157 self.clear_highlights::<InputComposition>(cx);
27158 self.ime_transaction.take();
27159 }
27160
27161 fn replace_text_in_range(
27162 &mut self,
27163 range_utf16: Option<Range<usize>>,
27164 text: &str,
27165 window: &mut Window,
27166 cx: &mut Context<Self>,
27167 ) {
27168 if !self.input_enabled {
27169 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27170 return;
27171 }
27172
27173 self.transact(window, cx, |this, window, cx| {
27174 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27175 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27176 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27177 Some(this.selection_replacement_ranges(range_utf16, cx))
27178 } else {
27179 this.marked_text_ranges(cx)
27180 };
27181
27182 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27183 let newest_selection_id = this.selections.newest_anchor().id;
27184 this.selections
27185 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27186 .iter()
27187 .zip(ranges_to_replace.iter())
27188 .find_map(|(selection, range)| {
27189 if selection.id == newest_selection_id {
27190 Some(
27191 (range.start.0.0 as isize - selection.head().0.0 as isize)
27192 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27193 )
27194 } else {
27195 None
27196 }
27197 })
27198 });
27199
27200 cx.emit(EditorEvent::InputHandled {
27201 utf16_range_to_replace: range_to_replace,
27202 text: text.into(),
27203 });
27204
27205 if let Some(new_selected_ranges) = new_selected_ranges {
27206 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27207 selections.select_ranges(new_selected_ranges)
27208 });
27209 this.backspace(&Default::default(), window, cx);
27210 }
27211
27212 this.handle_input(text, window, cx);
27213 });
27214
27215 if let Some(transaction) = self.ime_transaction {
27216 self.buffer.update(cx, |buffer, cx| {
27217 buffer.group_until_transaction(transaction, cx);
27218 });
27219 }
27220
27221 self.unmark_text(window, cx);
27222 }
27223
27224 fn replace_and_mark_text_in_range(
27225 &mut self,
27226 range_utf16: Option<Range<usize>>,
27227 text: &str,
27228 new_selected_range_utf16: Option<Range<usize>>,
27229 window: &mut Window,
27230 cx: &mut Context<Self>,
27231 ) {
27232 if !self.input_enabled {
27233 return;
27234 }
27235
27236 let transaction = self.transact(window, cx, |this, window, cx| {
27237 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27238 let snapshot = this.buffer.read(cx).read(cx);
27239 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27240 for marked_range in &mut marked_ranges {
27241 marked_range.end = marked_range.start + relative_range_utf16.end;
27242 marked_range.start += relative_range_utf16.start;
27243 marked_range.start =
27244 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27245 marked_range.end =
27246 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27247 }
27248 }
27249 Some(marked_ranges)
27250 } else if let Some(range_utf16) = range_utf16 {
27251 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27252 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27253 Some(this.selection_replacement_ranges(range_utf16, cx))
27254 } else {
27255 None
27256 };
27257
27258 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27259 let newest_selection_id = this.selections.newest_anchor().id;
27260 this.selections
27261 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27262 .iter()
27263 .zip(ranges_to_replace.iter())
27264 .find_map(|(selection, range)| {
27265 if selection.id == newest_selection_id {
27266 Some(
27267 (range.start.0.0 as isize - selection.head().0.0 as isize)
27268 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27269 )
27270 } else {
27271 None
27272 }
27273 })
27274 });
27275
27276 cx.emit(EditorEvent::InputHandled {
27277 utf16_range_to_replace: range_to_replace,
27278 text: text.into(),
27279 });
27280
27281 if let Some(ranges) = ranges_to_replace {
27282 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27283 s.select_ranges(ranges)
27284 });
27285 }
27286
27287 let marked_ranges = {
27288 let snapshot = this.buffer.read(cx).read(cx);
27289 this.selections
27290 .disjoint_anchors_arc()
27291 .iter()
27292 .map(|selection| {
27293 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27294 })
27295 .collect::<Vec<_>>()
27296 };
27297
27298 if text.is_empty() {
27299 this.unmark_text(window, cx);
27300 } else {
27301 this.highlight_text::<InputComposition>(
27302 marked_ranges.clone(),
27303 HighlightStyle {
27304 underline: Some(UnderlineStyle {
27305 thickness: px(1.),
27306 color: None,
27307 wavy: false,
27308 }),
27309 ..Default::default()
27310 },
27311 cx,
27312 );
27313 }
27314
27315 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27316 let use_autoclose = this.use_autoclose;
27317 let use_auto_surround = this.use_auto_surround;
27318 this.set_use_autoclose(false);
27319 this.set_use_auto_surround(false);
27320 this.handle_input(text, window, cx);
27321 this.set_use_autoclose(use_autoclose);
27322 this.set_use_auto_surround(use_auto_surround);
27323
27324 if let Some(new_selected_range) = new_selected_range_utf16 {
27325 let snapshot = this.buffer.read(cx).read(cx);
27326 let new_selected_ranges = marked_ranges
27327 .into_iter()
27328 .map(|marked_range| {
27329 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27330 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27331 insertion_start.0 + new_selected_range.start,
27332 ));
27333 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27334 insertion_start.0 + new_selected_range.end,
27335 ));
27336 snapshot.clip_offset_utf16(new_start, Bias::Left)
27337 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27338 })
27339 .collect::<Vec<_>>();
27340
27341 drop(snapshot);
27342 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27343 selections.select_ranges(new_selected_ranges)
27344 });
27345 }
27346 });
27347
27348 self.ime_transaction = self.ime_transaction.or(transaction);
27349 if let Some(transaction) = self.ime_transaction {
27350 self.buffer.update(cx, |buffer, cx| {
27351 buffer.group_until_transaction(transaction, cx);
27352 });
27353 }
27354
27355 if self.text_highlights::<InputComposition>(cx).is_none() {
27356 self.ime_transaction.take();
27357 }
27358 }
27359
27360 fn bounds_for_range(
27361 &mut self,
27362 range_utf16: Range<usize>,
27363 element_bounds: gpui::Bounds<Pixels>,
27364 window: &mut Window,
27365 cx: &mut Context<Self>,
27366 ) -> Option<gpui::Bounds<Pixels>> {
27367 let text_layout_details = self.text_layout_details(window);
27368 let CharacterDimensions {
27369 em_width,
27370 em_advance,
27371 line_height,
27372 } = self.character_dimensions(window);
27373
27374 let snapshot = self.snapshot(window, cx);
27375 let scroll_position = snapshot.scroll_position();
27376 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
27377
27378 let start =
27379 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
27380 let x = Pixels::from(
27381 ScrollOffset::from(
27382 snapshot.x_for_display_point(start, &text_layout_details)
27383 + self.gutter_dimensions.full_width(),
27384 ) - scroll_left,
27385 );
27386 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
27387
27388 Some(Bounds {
27389 origin: element_bounds.origin + point(x, y),
27390 size: size(em_width, line_height),
27391 })
27392 }
27393
27394 fn character_index_for_point(
27395 &mut self,
27396 point: gpui::Point<Pixels>,
27397 _window: &mut Window,
27398 _cx: &mut Context<Self>,
27399 ) -> Option<usize> {
27400 let position_map = self.last_position_map.as_ref()?;
27401 if !position_map.text_hitbox.contains(&point) {
27402 return None;
27403 }
27404 let display_point = position_map.point_for_position(point).previous_valid;
27405 let anchor = position_map
27406 .snapshot
27407 .display_point_to_anchor(display_point, Bias::Left);
27408 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
27409 Some(utf16_offset.0.0)
27410 }
27411
27412 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
27413 self.input_enabled
27414 }
27415}
27416
27417trait SelectionExt {
27418 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
27419 fn spanned_rows(
27420 &self,
27421 include_end_if_at_line_start: bool,
27422 map: &DisplaySnapshot,
27423 ) -> Range<MultiBufferRow>;
27424}
27425
27426impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
27427 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
27428 let start = self
27429 .start
27430 .to_point(map.buffer_snapshot())
27431 .to_display_point(map);
27432 let end = self
27433 .end
27434 .to_point(map.buffer_snapshot())
27435 .to_display_point(map);
27436 if self.reversed {
27437 end..start
27438 } else {
27439 start..end
27440 }
27441 }
27442
27443 fn spanned_rows(
27444 &self,
27445 include_end_if_at_line_start: bool,
27446 map: &DisplaySnapshot,
27447 ) -> Range<MultiBufferRow> {
27448 let start = self.start.to_point(map.buffer_snapshot());
27449 let mut end = self.end.to_point(map.buffer_snapshot());
27450 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
27451 end.row -= 1;
27452 }
27453
27454 let buffer_start = map.prev_line_boundary(start).0;
27455 let buffer_end = map.next_line_boundary(end).0;
27456 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
27457 }
27458}
27459
27460impl<T: InvalidationRegion> InvalidationStack<T> {
27461 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
27462 where
27463 S: Clone + ToOffset,
27464 {
27465 while let Some(region) = self.last() {
27466 let all_selections_inside_invalidation_ranges =
27467 if selections.len() == region.ranges().len() {
27468 selections
27469 .iter()
27470 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
27471 .all(|(selection, invalidation_range)| {
27472 let head = selection.head().to_offset(buffer);
27473 invalidation_range.start <= head && invalidation_range.end >= head
27474 })
27475 } else {
27476 false
27477 };
27478
27479 if all_selections_inside_invalidation_ranges {
27480 break;
27481 } else {
27482 self.pop();
27483 }
27484 }
27485 }
27486}
27487
27488impl<T> Default for InvalidationStack<T> {
27489 fn default() -> Self {
27490 Self(Default::default())
27491 }
27492}
27493
27494impl<T> Deref for InvalidationStack<T> {
27495 type Target = Vec<T>;
27496
27497 fn deref(&self) -> &Self::Target {
27498 &self.0
27499 }
27500}
27501
27502impl<T> DerefMut for InvalidationStack<T> {
27503 fn deref_mut(&mut self) -> &mut Self::Target {
27504 &mut self.0
27505 }
27506}
27507
27508impl InvalidationRegion for SnippetState {
27509 fn ranges(&self) -> &[Range<Anchor>] {
27510 &self.ranges[self.active_index]
27511 }
27512}
27513
27514fn edit_prediction_edit_text(
27515 current_snapshot: &BufferSnapshot,
27516 edits: &[(Range<Anchor>, impl AsRef<str>)],
27517 edit_preview: &EditPreview,
27518 include_deletions: bool,
27519 cx: &App,
27520) -> HighlightedText {
27521 let edits = edits
27522 .iter()
27523 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
27524 .collect::<Vec<_>>();
27525
27526 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
27527}
27528
27529fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
27530 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
27531 // Just show the raw edit text with basic styling
27532 let mut text = String::new();
27533 let mut highlights = Vec::new();
27534
27535 let insertion_highlight_style = HighlightStyle {
27536 color: Some(cx.theme().colors().text),
27537 ..Default::default()
27538 };
27539
27540 for (_, edit_text) in edits {
27541 let start_offset = text.len();
27542 text.push_str(edit_text);
27543 let end_offset = text.len();
27544
27545 if start_offset < end_offset {
27546 highlights.push((start_offset..end_offset, insertion_highlight_style));
27547 }
27548 }
27549
27550 HighlightedText {
27551 text: text.into(),
27552 highlights,
27553 }
27554}
27555
27556pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
27557 match severity {
27558 lsp::DiagnosticSeverity::ERROR => colors.error,
27559 lsp::DiagnosticSeverity::WARNING => colors.warning,
27560 lsp::DiagnosticSeverity::INFORMATION => colors.info,
27561 lsp::DiagnosticSeverity::HINT => colors.info,
27562 _ => colors.ignored,
27563 }
27564}
27565
27566pub fn styled_runs_for_code_label<'a>(
27567 label: &'a CodeLabel,
27568 syntax_theme: &'a theme::SyntaxTheme,
27569 local_player: &'a theme::PlayerColor,
27570) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
27571 let fade_out = HighlightStyle {
27572 fade_out: Some(0.35),
27573 ..Default::default()
27574 };
27575
27576 let mut prev_end = label.filter_range.end;
27577 label
27578 .runs
27579 .iter()
27580 .enumerate()
27581 .flat_map(move |(ix, (range, highlight_id))| {
27582 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
27583 HighlightStyle {
27584 color: Some(local_player.cursor),
27585 ..Default::default()
27586 }
27587 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
27588 HighlightStyle {
27589 background_color: Some(local_player.selection),
27590 ..Default::default()
27591 }
27592 } else if let Some(style) = highlight_id.style(syntax_theme) {
27593 style
27594 } else {
27595 return Default::default();
27596 };
27597 let muted_style = style.highlight(fade_out);
27598
27599 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
27600 if range.start >= label.filter_range.end {
27601 if range.start > prev_end {
27602 runs.push((prev_end..range.start, fade_out));
27603 }
27604 runs.push((range.clone(), muted_style));
27605 } else if range.end <= label.filter_range.end {
27606 runs.push((range.clone(), style));
27607 } else {
27608 runs.push((range.start..label.filter_range.end, style));
27609 runs.push((label.filter_range.end..range.end, muted_style));
27610 }
27611 prev_end = cmp::max(prev_end, range.end);
27612
27613 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
27614 runs.push((prev_end..label.text.len(), fade_out));
27615 }
27616
27617 runs
27618 })
27619}
27620
27621pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
27622 let mut prev_index = 0;
27623 let mut prev_codepoint: Option<char> = None;
27624 text.char_indices()
27625 .chain([(text.len(), '\0')])
27626 .filter_map(move |(index, codepoint)| {
27627 let prev_codepoint = prev_codepoint.replace(codepoint)?;
27628 let is_boundary = index == text.len()
27629 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
27630 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
27631 if is_boundary {
27632 let chunk = &text[prev_index..index];
27633 prev_index = index;
27634 Some(chunk)
27635 } else {
27636 None
27637 }
27638 })
27639}
27640
27641/// Given a string of text immediately before the cursor, iterates over possible
27642/// strings a snippet could match to. More precisely: returns an iterator over
27643/// suffixes of `text` created by splitting at word boundaries (before & after
27644/// every non-word character).
27645///
27646/// Shorter suffixes are returned first.
27647pub(crate) fn snippet_candidate_suffixes(
27648 text: &str,
27649 is_word_char: impl Fn(char) -> bool,
27650) -> impl std::iter::Iterator<Item = &str> {
27651 let mut prev_index = text.len();
27652 let mut prev_codepoint = None;
27653 text.char_indices()
27654 .rev()
27655 .chain([(0, '\0')])
27656 .filter_map(move |(index, codepoint)| {
27657 let prev_index = std::mem::replace(&mut prev_index, index);
27658 let prev_codepoint = prev_codepoint.replace(codepoint)?;
27659 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
27660 None
27661 } else {
27662 let chunk = &text[prev_index..]; // go to end of string
27663 Some(chunk)
27664 }
27665 })
27666}
27667
27668pub trait RangeToAnchorExt: Sized {
27669 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
27670
27671 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
27672 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
27673 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
27674 }
27675}
27676
27677impl<T: ToOffset> RangeToAnchorExt for Range<T> {
27678 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
27679 let start_offset = self.start.to_offset(snapshot);
27680 let end_offset = self.end.to_offset(snapshot);
27681 if start_offset == end_offset {
27682 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
27683 } else {
27684 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
27685 }
27686 }
27687}
27688
27689pub trait RowExt {
27690 fn as_f64(&self) -> f64;
27691
27692 fn next_row(&self) -> Self;
27693
27694 fn previous_row(&self) -> Self;
27695
27696 fn minus(&self, other: Self) -> u32;
27697}
27698
27699impl RowExt for DisplayRow {
27700 fn as_f64(&self) -> f64 {
27701 self.0 as _
27702 }
27703
27704 fn next_row(&self) -> Self {
27705 Self(self.0 + 1)
27706 }
27707
27708 fn previous_row(&self) -> Self {
27709 Self(self.0.saturating_sub(1))
27710 }
27711
27712 fn minus(&self, other: Self) -> u32 {
27713 self.0 - other.0
27714 }
27715}
27716
27717impl RowExt for MultiBufferRow {
27718 fn as_f64(&self) -> f64 {
27719 self.0 as _
27720 }
27721
27722 fn next_row(&self) -> Self {
27723 Self(self.0 + 1)
27724 }
27725
27726 fn previous_row(&self) -> Self {
27727 Self(self.0.saturating_sub(1))
27728 }
27729
27730 fn minus(&self, other: Self) -> u32 {
27731 self.0 - other.0
27732 }
27733}
27734
27735trait RowRangeExt {
27736 type Row;
27737
27738 fn len(&self) -> usize;
27739
27740 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
27741}
27742
27743impl RowRangeExt for Range<MultiBufferRow> {
27744 type Row = MultiBufferRow;
27745
27746 fn len(&self) -> usize {
27747 (self.end.0 - self.start.0) as usize
27748 }
27749
27750 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
27751 (self.start.0..self.end.0).map(MultiBufferRow)
27752 }
27753}
27754
27755impl RowRangeExt for Range<DisplayRow> {
27756 type Row = DisplayRow;
27757
27758 fn len(&self) -> usize {
27759 (self.end.0 - self.start.0) as usize
27760 }
27761
27762 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
27763 (self.start.0..self.end.0).map(DisplayRow)
27764 }
27765}
27766
27767/// If select range has more than one line, we
27768/// just point the cursor to range.start.
27769fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
27770 if range.start.row == range.end.row {
27771 range
27772 } else {
27773 range.start..range.start
27774 }
27775}
27776pub struct KillRing(ClipboardItem);
27777impl Global for KillRing {}
27778
27779const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
27780
27781enum BreakpointPromptEditAction {
27782 Log,
27783 Condition,
27784 HitCondition,
27785}
27786
27787struct BreakpointPromptEditor {
27788 pub(crate) prompt: Entity<Editor>,
27789 editor: WeakEntity<Editor>,
27790 breakpoint_anchor: Anchor,
27791 breakpoint: Breakpoint,
27792 edit_action: BreakpointPromptEditAction,
27793 block_ids: HashSet<CustomBlockId>,
27794 editor_margins: Arc<Mutex<EditorMargins>>,
27795 _subscriptions: Vec<Subscription>,
27796}
27797
27798impl BreakpointPromptEditor {
27799 const MAX_LINES: u8 = 4;
27800
27801 fn new(
27802 editor: WeakEntity<Editor>,
27803 breakpoint_anchor: Anchor,
27804 breakpoint: Breakpoint,
27805 edit_action: BreakpointPromptEditAction,
27806 window: &mut Window,
27807 cx: &mut Context<Self>,
27808 ) -> Self {
27809 let base_text = match edit_action {
27810 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
27811 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
27812 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
27813 }
27814 .map(|msg| msg.to_string())
27815 .unwrap_or_default();
27816
27817 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
27818 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27819
27820 let prompt = cx.new(|cx| {
27821 let mut prompt = Editor::new(
27822 EditorMode::AutoHeight {
27823 min_lines: 1,
27824 max_lines: Some(Self::MAX_LINES as usize),
27825 },
27826 buffer,
27827 None,
27828 window,
27829 cx,
27830 );
27831 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
27832 prompt.set_show_cursor_when_unfocused(false, cx);
27833 prompt.set_placeholder_text(
27834 match edit_action {
27835 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
27836 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
27837 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
27838 },
27839 window,
27840 cx,
27841 );
27842
27843 prompt
27844 });
27845
27846 Self {
27847 prompt,
27848 editor,
27849 breakpoint_anchor,
27850 breakpoint,
27851 edit_action,
27852 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
27853 block_ids: Default::default(),
27854 _subscriptions: vec![],
27855 }
27856 }
27857
27858 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
27859 self.block_ids.extend(block_ids)
27860 }
27861
27862 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
27863 if let Some(editor) = self.editor.upgrade() {
27864 let message = self
27865 .prompt
27866 .read(cx)
27867 .buffer
27868 .read(cx)
27869 .as_singleton()
27870 .expect("A multi buffer in breakpoint prompt isn't possible")
27871 .read(cx)
27872 .as_rope()
27873 .to_string();
27874
27875 editor.update(cx, |editor, cx| {
27876 editor.edit_breakpoint_at_anchor(
27877 self.breakpoint_anchor,
27878 self.breakpoint.clone(),
27879 match self.edit_action {
27880 BreakpointPromptEditAction::Log => {
27881 BreakpointEditAction::EditLogMessage(message.into())
27882 }
27883 BreakpointPromptEditAction::Condition => {
27884 BreakpointEditAction::EditCondition(message.into())
27885 }
27886 BreakpointPromptEditAction::HitCondition => {
27887 BreakpointEditAction::EditHitCondition(message.into())
27888 }
27889 },
27890 cx,
27891 );
27892
27893 editor.remove_blocks(self.block_ids.clone(), None, cx);
27894 cx.focus_self(window);
27895 });
27896 }
27897 }
27898
27899 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
27900 self.editor
27901 .update(cx, |editor, cx| {
27902 editor.remove_blocks(self.block_ids.clone(), None, cx);
27903 window.focus(&editor.focus_handle, cx);
27904 })
27905 .log_err();
27906 }
27907
27908 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
27909 let settings = ThemeSettings::get_global(cx);
27910 let text_style = TextStyle {
27911 color: if self.prompt.read(cx).read_only(cx) {
27912 cx.theme().colors().text_disabled
27913 } else {
27914 cx.theme().colors().text
27915 },
27916 font_family: settings.buffer_font.family.clone(),
27917 font_fallbacks: settings.buffer_font.fallbacks.clone(),
27918 font_size: settings.buffer_font_size(cx).into(),
27919 font_weight: settings.buffer_font.weight,
27920 line_height: relative(settings.buffer_line_height.value()),
27921 ..Default::default()
27922 };
27923 EditorElement::new(
27924 &self.prompt,
27925 EditorStyle {
27926 background: cx.theme().colors().editor_background,
27927 local_player: cx.theme().players().local(),
27928 text: text_style,
27929 ..Default::default()
27930 },
27931 )
27932 }
27933}
27934
27935impl Render for BreakpointPromptEditor {
27936 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27937 let editor_margins = *self.editor_margins.lock();
27938 let gutter_dimensions = editor_margins.gutter;
27939 h_flex()
27940 .key_context("Editor")
27941 .bg(cx.theme().colors().editor_background)
27942 .border_y_1()
27943 .border_color(cx.theme().status().info_border)
27944 .size_full()
27945 .py(window.line_height() / 2.5)
27946 .on_action(cx.listener(Self::confirm))
27947 .on_action(cx.listener(Self::cancel))
27948 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
27949 .child(div().flex_1().child(self.render_prompt_editor(cx)))
27950 }
27951}
27952
27953impl Focusable for BreakpointPromptEditor {
27954 fn focus_handle(&self, cx: &App) -> FocusHandle {
27955 self.prompt.focus_handle(cx)
27956 }
27957}
27958
27959fn all_edits_insertions_or_deletions(
27960 edits: &Vec<(Range<Anchor>, Arc<str>)>,
27961 snapshot: &MultiBufferSnapshot,
27962) -> bool {
27963 let mut all_insertions = true;
27964 let mut all_deletions = true;
27965
27966 for (range, new_text) in edits.iter() {
27967 let range_is_empty = range.to_offset(snapshot).is_empty();
27968 let text_is_empty = new_text.is_empty();
27969
27970 if range_is_empty != text_is_empty {
27971 if range_is_empty {
27972 all_deletions = false;
27973 } else {
27974 all_insertions = false;
27975 }
27976 } else {
27977 return false;
27978 }
27979
27980 if !all_insertions && !all_deletions {
27981 return false;
27982 }
27983 }
27984 all_insertions || all_deletions
27985}
27986
27987struct MissingEditPredictionKeybindingTooltip;
27988
27989impl Render for MissingEditPredictionKeybindingTooltip {
27990 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27991 ui::tooltip_container(cx, |container, cx| {
27992 container
27993 .flex_shrink_0()
27994 .max_w_80()
27995 .min_h(rems_from_px(124.))
27996 .justify_between()
27997 .child(
27998 v_flex()
27999 .flex_1()
28000 .text_ui_sm(cx)
28001 .child(Label::new("Conflict with Accept Keybinding"))
28002 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28003 )
28004 .child(
28005 h_flex()
28006 .pb_1()
28007 .gap_1()
28008 .items_end()
28009 .w_full()
28010 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28011 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28012 }))
28013 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28014 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28015 })),
28016 )
28017 })
28018 }
28019}
28020
28021#[derive(Debug, Clone, Copy, PartialEq)]
28022pub struct LineHighlight {
28023 pub background: Background,
28024 pub border: Option<gpui::Hsla>,
28025 pub include_gutter: bool,
28026 pub type_id: Option<TypeId>,
28027}
28028
28029struct LineManipulationResult {
28030 pub new_text: String,
28031 pub line_count_before: usize,
28032 pub line_count_after: usize,
28033}
28034
28035fn render_diff_hunk_controls(
28036 row: u32,
28037 status: &DiffHunkStatus,
28038 hunk_range: Range<Anchor>,
28039 is_created_file: bool,
28040 line_height: Pixels,
28041 editor: &Entity<Editor>,
28042 _window: &mut Window,
28043 cx: &mut App,
28044) -> AnyElement {
28045 h_flex()
28046 .h(line_height)
28047 .mr_1()
28048 .gap_1()
28049 .px_0p5()
28050 .pb_1()
28051 .border_x_1()
28052 .border_b_1()
28053 .border_color(cx.theme().colors().border_variant)
28054 .rounded_b_lg()
28055 .bg(cx.theme().colors().editor_background)
28056 .gap_1()
28057 .block_mouse_except_scroll()
28058 .shadow_md()
28059 .child(if status.has_secondary_hunk() {
28060 Button::new(("stage", row as u64), "Stage")
28061 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28062 .tooltip({
28063 let focus_handle = editor.focus_handle(cx);
28064 move |_window, cx| {
28065 Tooltip::for_action_in(
28066 "Stage Hunk",
28067 &::git::ToggleStaged,
28068 &focus_handle,
28069 cx,
28070 )
28071 }
28072 })
28073 .on_click({
28074 let editor = editor.clone();
28075 move |_event, _window, cx| {
28076 editor.update(cx, |editor, cx| {
28077 editor.stage_or_unstage_diff_hunks(
28078 true,
28079 vec![hunk_range.start..hunk_range.start],
28080 cx,
28081 );
28082 });
28083 }
28084 })
28085 } else {
28086 Button::new(("unstage", row as u64), "Unstage")
28087 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28088 .tooltip({
28089 let focus_handle = editor.focus_handle(cx);
28090 move |_window, cx| {
28091 Tooltip::for_action_in(
28092 "Unstage Hunk",
28093 &::git::ToggleStaged,
28094 &focus_handle,
28095 cx,
28096 )
28097 }
28098 })
28099 .on_click({
28100 let editor = editor.clone();
28101 move |_event, _window, cx| {
28102 editor.update(cx, |editor, cx| {
28103 editor.stage_or_unstage_diff_hunks(
28104 false,
28105 vec![hunk_range.start..hunk_range.start],
28106 cx,
28107 );
28108 });
28109 }
28110 })
28111 })
28112 .child(
28113 Button::new(("restore", row as u64), "Restore")
28114 .tooltip({
28115 let focus_handle = editor.focus_handle(cx);
28116 move |_window, cx| {
28117 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28118 }
28119 })
28120 .on_click({
28121 let editor = editor.clone();
28122 move |_event, window, cx| {
28123 editor.update(cx, |editor, cx| {
28124 let snapshot = editor.snapshot(window, cx);
28125 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28126 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28127 });
28128 }
28129 })
28130 .disabled(is_created_file),
28131 )
28132 .when(
28133 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28134 |el| {
28135 el.child(
28136 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28137 .shape(IconButtonShape::Square)
28138 .icon_size(IconSize::Small)
28139 // .disabled(!has_multiple_hunks)
28140 .tooltip({
28141 let focus_handle = editor.focus_handle(cx);
28142 move |_window, cx| {
28143 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28144 }
28145 })
28146 .on_click({
28147 let editor = editor.clone();
28148 move |_event, window, cx| {
28149 editor.update(cx, |editor, cx| {
28150 let snapshot = editor.snapshot(window, cx);
28151 let position =
28152 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28153 editor.go_to_hunk_before_or_after_position(
28154 &snapshot,
28155 position,
28156 Direction::Next,
28157 window,
28158 cx,
28159 );
28160 editor.expand_selected_diff_hunks(cx);
28161 });
28162 }
28163 }),
28164 )
28165 .child(
28166 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28167 .shape(IconButtonShape::Square)
28168 .icon_size(IconSize::Small)
28169 // .disabled(!has_multiple_hunks)
28170 .tooltip({
28171 let focus_handle = editor.focus_handle(cx);
28172 move |_window, cx| {
28173 Tooltip::for_action_in(
28174 "Previous Hunk",
28175 &GoToPreviousHunk,
28176 &focus_handle,
28177 cx,
28178 )
28179 }
28180 })
28181 .on_click({
28182 let editor = editor.clone();
28183 move |_event, window, cx| {
28184 editor.update(cx, |editor, cx| {
28185 let snapshot = editor.snapshot(window, cx);
28186 let point =
28187 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28188 editor.go_to_hunk_before_or_after_position(
28189 &snapshot,
28190 point,
28191 Direction::Prev,
28192 window,
28193 cx,
28194 );
28195 editor.expand_selected_diff_hunks(cx);
28196 });
28197 }
28198 }),
28199 )
28200 },
28201 )
28202 .into_any_element()
28203}
28204
28205pub fn multibuffer_context_lines(cx: &App) -> u32 {
28206 EditorSettings::try_get(cx)
28207 .map(|settings| settings.excerpt_context_lines)
28208 .unwrap_or(2)
28209 .min(32)
28210}