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, Size, Stateful, Styled, Subscription, Task, TextRun,
112 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
113 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
114 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 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 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/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1035///
1036/// See the [module level documentation](self) for more information.
1037pub struct Editor {
1038 focus_handle: FocusHandle,
1039 last_focused_descendant: Option<WeakFocusHandle>,
1040 /// The text buffer being edited
1041 buffer: Entity<MultiBuffer>,
1042 /// Map of how text in the buffer should be displayed.
1043 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1044 pub display_map: Entity<DisplayMap>,
1045 placeholder_display_map: Option<Entity<DisplayMap>>,
1046 pub selections: SelectionsCollection,
1047 pub scroll_manager: ScrollManager,
1048 /// When inline assist editors are linked, they all render cursors because
1049 /// typing enters text into each of them, even the ones that aren't focused.
1050 pub(crate) show_cursor_when_unfocused: bool,
1051 columnar_selection_state: Option<ColumnarSelectionState>,
1052 add_selections_state: Option<AddSelectionsState>,
1053 select_next_state: Option<SelectNextState>,
1054 select_prev_state: Option<SelectNextState>,
1055 selection_history: SelectionHistory,
1056 defer_selection_effects: bool,
1057 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1058 autoclose_regions: Vec<AutocloseRegion>,
1059 snippet_stack: InvalidationStack<SnippetState>,
1060 select_syntax_node_history: SelectSyntaxNodeHistory,
1061 ime_transaction: Option<TransactionId>,
1062 pub diagnostics_max_severity: DiagnosticSeverity,
1063 active_diagnostics: ActiveDiagnostic,
1064 show_inline_diagnostics: bool,
1065 inline_diagnostics_update: Task<()>,
1066 inline_diagnostics_enabled: bool,
1067 diagnostics_enabled: bool,
1068 word_completions_enabled: bool,
1069 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1070 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1071 hard_wrap: Option<usize>,
1072 project: Option<Entity<Project>>,
1073 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1074 completion_provider: Option<Rc<dyn CompletionProvider>>,
1075 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1076 blink_manager: Entity<BlinkManager>,
1077 show_cursor_names: bool,
1078 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1079 pub show_local_selections: bool,
1080 mode: EditorMode,
1081 show_breadcrumbs: bool,
1082 show_gutter: bool,
1083 show_scrollbars: ScrollbarAxes,
1084 minimap_visibility: MinimapVisibility,
1085 offset_content: bool,
1086 disable_expand_excerpt_buttons: bool,
1087 delegate_expand_excerpts: bool,
1088 show_line_numbers: Option<bool>,
1089 use_relative_line_numbers: Option<bool>,
1090 show_git_diff_gutter: Option<bool>,
1091 show_code_actions: Option<bool>,
1092 show_runnables: Option<bool>,
1093 show_breakpoints: Option<bool>,
1094 show_diff_review_button: bool,
1095 show_wrap_guides: Option<bool>,
1096 show_indent_guides: Option<bool>,
1097 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1098 highlight_order: usize,
1099 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1100 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1101 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1102 scrollbar_marker_state: ScrollbarMarkerState,
1103 active_indent_guides_state: ActiveIndentGuidesState,
1104 nav_history: Option<ItemNavHistory>,
1105 context_menu: RefCell<Option<CodeContextMenu>>,
1106 context_menu_options: Option<ContextMenuOptions>,
1107 mouse_context_menu: Option<MouseContextMenu>,
1108 completion_tasks: Vec<(CompletionId, Task<()>)>,
1109 inline_blame_popover: Option<InlineBlamePopover>,
1110 inline_blame_popover_show_task: Option<Task<()>>,
1111 signature_help_state: SignatureHelpState,
1112 auto_signature_help: Option<bool>,
1113 find_all_references_task_sources: Vec<Anchor>,
1114 next_completion_id: CompletionId,
1115 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1116 code_actions_task: Option<Task<Result<()>>>,
1117 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1118 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1119 debounced_selection_highlight_complete: bool,
1120 document_highlights_task: Option<Task<()>>,
1121 linked_editing_range_task: Option<Task<Option<()>>>,
1122 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1123 pending_rename: Option<RenameState>,
1124 searchable: bool,
1125 cursor_shape: CursorShape,
1126 /// Whether the cursor is offset one character to the left when something is
1127 /// selected (needed for vim visual mode)
1128 cursor_offset_on_selection: bool,
1129 current_line_highlight: Option<CurrentLineHighlight>,
1130 pub collapse_matches: bool,
1131 autoindent_mode: Option<AutoindentMode>,
1132 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1133 input_enabled: bool,
1134 use_modal_editing: bool,
1135 read_only: bool,
1136 leader_id: Option<CollaboratorId>,
1137 remote_id: Option<ViewId>,
1138 pub hover_state: HoverState,
1139 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1140 prev_pressure_stage: Option<PressureStage>,
1141 gutter_hovered: bool,
1142 hovered_link_state: Option<HoveredLinkState>,
1143 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1144 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1145 active_edit_prediction: Option<EditPredictionState>,
1146 /// Used to prevent flickering as the user types while the menu is open
1147 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1148 edit_prediction_settings: EditPredictionSettings,
1149 edit_predictions_hidden_for_vim_mode: bool,
1150 show_edit_predictions_override: Option<bool>,
1151 show_completions_on_input_override: Option<bool>,
1152 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1153 edit_prediction_preview: EditPredictionPreview,
1154 edit_prediction_indent_conflict: bool,
1155 edit_prediction_requires_modifier_in_indent_conflict: bool,
1156 next_inlay_id: usize,
1157 next_color_inlay_id: usize,
1158 _subscriptions: Vec<Subscription>,
1159 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1160 gutter_dimensions: GutterDimensions,
1161 style: Option<EditorStyle>,
1162 text_style_refinement: Option<TextStyleRefinement>,
1163 next_editor_action_id: EditorActionId,
1164 editor_actions: Rc<
1165 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1166 >,
1167 use_autoclose: bool,
1168 use_auto_surround: bool,
1169 auto_replace_emoji_shortcode: bool,
1170 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1171 show_git_blame_gutter: bool,
1172 show_git_blame_inline: bool,
1173 show_git_blame_inline_delay_task: Option<Task<()>>,
1174 git_blame_inline_enabled: bool,
1175 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1176 buffer_serialization: Option<BufferSerialization>,
1177 show_selection_menu: Option<bool>,
1178 blame: Option<Entity<GitBlame>>,
1179 blame_subscription: Option<Subscription>,
1180 custom_context_menu: Option<
1181 Box<
1182 dyn 'static
1183 + Fn(
1184 &mut Self,
1185 DisplayPoint,
1186 &mut Window,
1187 &mut Context<Self>,
1188 ) -> Option<Entity<ui::ContextMenu>>,
1189 >,
1190 >,
1191 last_bounds: Option<Bounds<Pixels>>,
1192 last_position_map: Option<Rc<PositionMap>>,
1193 expect_bounds_change: Option<Bounds<Pixels>>,
1194 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1195 tasks_update_task: Option<Task<()>>,
1196 breakpoint_store: Option<Entity<BreakpointStore>>,
1197 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1198 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1199 hovered_diff_hunk_row: Option<DisplayRow>,
1200 pull_diagnostics_task: Task<()>,
1201 pull_diagnostics_background_task: Task<()>,
1202 in_project_search: bool,
1203 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1204 breadcrumb_header: Option<String>,
1205 focused_block: Option<FocusedBlock>,
1206 next_scroll_position: NextScrollCursorCenterTopBottom,
1207 addons: HashMap<TypeId, Box<dyn Addon>>,
1208 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1209 load_diff_task: Option<Shared<Task<()>>>,
1210 /// Whether we are temporarily displaying a diff other than git's
1211 temporary_diff_override: bool,
1212 selection_mark_mode: bool,
1213 toggle_fold_multiple_buffers: Task<()>,
1214 _scroll_cursor_center_top_bottom_task: Task<()>,
1215 serialize_selections: Task<()>,
1216 serialize_folds: Task<()>,
1217 mouse_cursor_hidden: bool,
1218 minimap: Option<Entity<Self>>,
1219 hide_mouse_mode: HideMouseMode,
1220 pub change_list: ChangeList,
1221 inline_value_cache: InlineValueCache,
1222 number_deleted_lines: bool,
1223
1224 selection_drag_state: SelectionDragState,
1225 colors: Option<LspColorData>,
1226 post_scroll_update: Task<()>,
1227 refresh_colors_task: Task<()>,
1228 inlay_hints: Option<LspInlayHintData>,
1229 folding_newlines: Task<()>,
1230 select_next_is_case_sensitive: Option<bool>,
1231 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1232 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1233 accent_data: Option<AccentData>,
1234 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1235}
1236
1237#[derive(Debug, PartialEq)]
1238struct AccentData {
1239 colors: AccentColors,
1240 overrides: Vec<SharedString>,
1241}
1242
1243fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1244 if debounce_ms > 0 {
1245 Some(Duration::from_millis(debounce_ms))
1246 } else {
1247 None
1248 }
1249}
1250
1251#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1252enum NextScrollCursorCenterTopBottom {
1253 #[default]
1254 Center,
1255 Top,
1256 Bottom,
1257}
1258
1259impl NextScrollCursorCenterTopBottom {
1260 fn next(&self) -> Self {
1261 match self {
1262 Self::Center => Self::Top,
1263 Self::Top => Self::Bottom,
1264 Self::Bottom => Self::Center,
1265 }
1266 }
1267}
1268
1269#[derive(Clone)]
1270pub struct EditorSnapshot {
1271 pub mode: EditorMode,
1272 show_gutter: bool,
1273 offset_content: bool,
1274 show_line_numbers: Option<bool>,
1275 number_deleted_lines: bool,
1276 show_git_diff_gutter: Option<bool>,
1277 show_code_actions: Option<bool>,
1278 show_runnables: Option<bool>,
1279 show_breakpoints: Option<bool>,
1280 git_blame_gutter_max_author_length: Option<usize>,
1281 pub display_snapshot: DisplaySnapshot,
1282 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1283 is_focused: bool,
1284 scroll_anchor: ScrollAnchor,
1285 ongoing_scroll: OngoingScroll,
1286 current_line_highlight: CurrentLineHighlight,
1287 gutter_hovered: bool,
1288}
1289
1290#[derive(Default, Debug, Clone, Copy)]
1291pub struct GutterDimensions {
1292 pub left_padding: Pixels,
1293 pub right_padding: Pixels,
1294 pub width: Pixels,
1295 pub margin: Pixels,
1296 pub git_blame_entries_width: Option<Pixels>,
1297}
1298
1299impl GutterDimensions {
1300 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1301 Self {
1302 margin: Self::default_gutter_margin(font_id, font_size, cx),
1303 ..Default::default()
1304 }
1305 }
1306
1307 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1308 -cx.text_system().descent(font_id, font_size)
1309 }
1310 /// The full width of the space taken up by the gutter.
1311 pub fn full_width(&self) -> Pixels {
1312 self.margin + self.width
1313 }
1314
1315 /// The width of the space reserved for the fold indicators,
1316 /// use alongside 'justify_end' and `gutter_width` to
1317 /// right align content with the line numbers
1318 pub fn fold_area_width(&self) -> Pixels {
1319 self.margin + self.right_padding
1320 }
1321}
1322
1323struct CharacterDimensions {
1324 em_width: Pixels,
1325 em_advance: Pixels,
1326 line_height: Pixels,
1327}
1328
1329#[derive(Debug)]
1330pub struct RemoteSelection {
1331 pub replica_id: ReplicaId,
1332 pub selection: Selection<Anchor>,
1333 pub cursor_shape: CursorShape,
1334 pub collaborator_id: CollaboratorId,
1335 pub line_mode: bool,
1336 pub user_name: Option<SharedString>,
1337 pub color: PlayerColor,
1338}
1339
1340#[derive(Clone, Debug)]
1341struct SelectionHistoryEntry {
1342 selections: Arc<[Selection<Anchor>]>,
1343 select_next_state: Option<SelectNextState>,
1344 select_prev_state: Option<SelectNextState>,
1345 add_selections_state: Option<AddSelectionsState>,
1346}
1347
1348#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1349enum SelectionHistoryMode {
1350 #[default]
1351 Normal,
1352 Undoing,
1353 Redoing,
1354 Skipping,
1355}
1356
1357#[derive(Clone, PartialEq, Eq, Hash)]
1358struct HoveredCursor {
1359 replica_id: ReplicaId,
1360 selection_id: usize,
1361}
1362
1363#[derive(Debug)]
1364/// SelectionEffects controls the side-effects of updating the selection.
1365///
1366/// The default behaviour does "what you mostly want":
1367/// - it pushes to the nav history if the cursor moved by >10 lines
1368/// - it re-triggers completion requests
1369/// - it scrolls to fit
1370///
1371/// You might want to modify these behaviours. For example when doing a "jump"
1372/// like go to definition, we always want to add to nav history; but when scrolling
1373/// in vim mode we never do.
1374///
1375/// Similarly, you might want to disable scrolling if you don't want the viewport to
1376/// move.
1377#[derive(Clone)]
1378pub struct SelectionEffects {
1379 nav_history: Option<bool>,
1380 completions: bool,
1381 scroll: Option<Autoscroll>,
1382}
1383
1384impl Default for SelectionEffects {
1385 fn default() -> Self {
1386 Self {
1387 nav_history: None,
1388 completions: true,
1389 scroll: Some(Autoscroll::fit()),
1390 }
1391 }
1392}
1393impl SelectionEffects {
1394 pub fn scroll(scroll: Autoscroll) -> Self {
1395 Self {
1396 scroll: Some(scroll),
1397 ..Default::default()
1398 }
1399 }
1400
1401 pub fn no_scroll() -> Self {
1402 Self {
1403 scroll: None,
1404 ..Default::default()
1405 }
1406 }
1407
1408 pub fn completions(self, completions: bool) -> Self {
1409 Self {
1410 completions,
1411 ..self
1412 }
1413 }
1414
1415 pub fn nav_history(self, nav_history: bool) -> Self {
1416 Self {
1417 nav_history: Some(nav_history),
1418 ..self
1419 }
1420 }
1421}
1422
1423struct DeferredSelectionEffectsState {
1424 changed: bool,
1425 effects: SelectionEffects,
1426 old_cursor_position: Anchor,
1427 history_entry: SelectionHistoryEntry,
1428}
1429
1430#[derive(Default)]
1431struct SelectionHistory {
1432 #[allow(clippy::type_complexity)]
1433 selections_by_transaction:
1434 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1435 mode: SelectionHistoryMode,
1436 undo_stack: VecDeque<SelectionHistoryEntry>,
1437 redo_stack: VecDeque<SelectionHistoryEntry>,
1438}
1439
1440impl SelectionHistory {
1441 #[track_caller]
1442 fn insert_transaction(
1443 &mut self,
1444 transaction_id: TransactionId,
1445 selections: Arc<[Selection<Anchor>]>,
1446 ) {
1447 if selections.is_empty() {
1448 log::error!(
1449 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1450 std::panic::Location::caller()
1451 );
1452 return;
1453 }
1454 self.selections_by_transaction
1455 .insert(transaction_id, (selections, None));
1456 }
1457
1458 #[allow(clippy::type_complexity)]
1459 fn transaction(
1460 &self,
1461 transaction_id: TransactionId,
1462 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1463 self.selections_by_transaction.get(&transaction_id)
1464 }
1465
1466 #[allow(clippy::type_complexity)]
1467 fn transaction_mut(
1468 &mut self,
1469 transaction_id: TransactionId,
1470 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1471 self.selections_by_transaction.get_mut(&transaction_id)
1472 }
1473
1474 fn push(&mut self, entry: SelectionHistoryEntry) {
1475 if !entry.selections.is_empty() {
1476 match self.mode {
1477 SelectionHistoryMode::Normal => {
1478 self.push_undo(entry);
1479 self.redo_stack.clear();
1480 }
1481 SelectionHistoryMode::Undoing => self.push_redo(entry),
1482 SelectionHistoryMode::Redoing => self.push_undo(entry),
1483 SelectionHistoryMode::Skipping => {}
1484 }
1485 }
1486 }
1487
1488 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1489 if self
1490 .undo_stack
1491 .back()
1492 .is_none_or(|e| e.selections != entry.selections)
1493 {
1494 self.undo_stack.push_back(entry);
1495 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1496 self.undo_stack.pop_front();
1497 }
1498 }
1499 }
1500
1501 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1502 if self
1503 .redo_stack
1504 .back()
1505 .is_none_or(|e| e.selections != entry.selections)
1506 {
1507 self.redo_stack.push_back(entry);
1508 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1509 self.redo_stack.pop_front();
1510 }
1511 }
1512 }
1513}
1514
1515#[derive(Clone, Copy)]
1516pub struct RowHighlightOptions {
1517 pub autoscroll: bool,
1518 pub include_gutter: bool,
1519}
1520
1521impl Default for RowHighlightOptions {
1522 fn default() -> Self {
1523 Self {
1524 autoscroll: Default::default(),
1525 include_gutter: true,
1526 }
1527 }
1528}
1529
1530struct RowHighlight {
1531 index: usize,
1532 range: Range<Anchor>,
1533 color: Hsla,
1534 options: RowHighlightOptions,
1535 type_id: TypeId,
1536}
1537
1538#[derive(Clone, Debug)]
1539struct AddSelectionsState {
1540 groups: Vec<AddSelectionsGroup>,
1541}
1542
1543#[derive(Clone, Debug)]
1544struct AddSelectionsGroup {
1545 above: bool,
1546 stack: Vec<usize>,
1547}
1548
1549#[derive(Clone)]
1550struct SelectNextState {
1551 query: AhoCorasick,
1552 wordwise: bool,
1553 done: bool,
1554}
1555
1556impl std::fmt::Debug for SelectNextState {
1557 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1558 f.debug_struct(std::any::type_name::<Self>())
1559 .field("wordwise", &self.wordwise)
1560 .field("done", &self.done)
1561 .finish()
1562 }
1563}
1564
1565#[derive(Debug)]
1566struct AutocloseRegion {
1567 selection_id: usize,
1568 range: Range<Anchor>,
1569 pair: BracketPair,
1570}
1571
1572#[derive(Debug)]
1573struct SnippetState {
1574 ranges: Vec<Vec<Range<Anchor>>>,
1575 active_index: usize,
1576 choices: Vec<Option<Vec<String>>>,
1577}
1578
1579#[doc(hidden)]
1580pub struct RenameState {
1581 pub range: Range<Anchor>,
1582 pub old_name: Arc<str>,
1583 pub editor: Entity<Editor>,
1584 block_id: CustomBlockId,
1585}
1586
1587struct InvalidationStack<T>(Vec<T>);
1588
1589struct RegisteredEditPredictionDelegate {
1590 provider: Arc<dyn EditPredictionDelegateHandle>,
1591 _subscription: Subscription,
1592}
1593
1594#[derive(Debug, PartialEq, Eq)]
1595pub struct ActiveDiagnosticGroup {
1596 pub active_range: Range<Anchor>,
1597 pub active_message: String,
1598 pub group_id: usize,
1599 pub blocks: HashSet<CustomBlockId>,
1600}
1601
1602#[derive(Debug, PartialEq, Eq)]
1603
1604pub(crate) enum ActiveDiagnostic {
1605 None,
1606 All,
1607 Group(ActiveDiagnosticGroup),
1608}
1609
1610#[derive(Serialize, Deserialize, Clone, Debug)]
1611pub struct ClipboardSelection {
1612 /// The number of bytes in this selection.
1613 pub len: usize,
1614 /// Whether this was a full-line selection.
1615 pub is_entire_line: bool,
1616 /// The indentation of the first line when this content was originally copied.
1617 pub first_line_indent: u32,
1618 #[serde(default)]
1619 pub file_path: Option<PathBuf>,
1620 #[serde(default)]
1621 pub line_range: Option<RangeInclusive<u32>>,
1622}
1623
1624impl ClipboardSelection {
1625 pub fn for_buffer(
1626 len: usize,
1627 is_entire_line: bool,
1628 range: Range<Point>,
1629 buffer: &MultiBufferSnapshot,
1630 project: Option<&Entity<Project>>,
1631 cx: &App,
1632 ) -> Self {
1633 let first_line_indent = buffer
1634 .indent_size_for_line(MultiBufferRow(range.start.row))
1635 .len;
1636
1637 let file_path = util::maybe!({
1638 let project = project?.read(cx);
1639 let file = buffer.file_at(range.start)?;
1640 let project_path = ProjectPath {
1641 worktree_id: file.worktree_id(cx),
1642 path: file.path().clone(),
1643 };
1644 project.absolute_path(&project_path, cx)
1645 });
1646
1647 let line_range = file_path.as_ref().and_then(|_| {
1648 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1649 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1650 if start_excerpt_id == end_excerpt_id {
1651 Some(start_point.row..=end_point.row)
1652 } else {
1653 None
1654 }
1655 });
1656
1657 Self {
1658 len,
1659 is_entire_line,
1660 first_line_indent,
1661 file_path,
1662 line_range,
1663 }
1664 }
1665}
1666
1667// selections, scroll behavior, was newest selection reversed
1668type SelectSyntaxNodeHistoryState = (
1669 Box<[Selection<MultiBufferOffset>]>,
1670 SelectSyntaxNodeScrollBehavior,
1671 bool,
1672);
1673
1674#[derive(Default)]
1675struct SelectSyntaxNodeHistory {
1676 stack: Vec<SelectSyntaxNodeHistoryState>,
1677 // disable temporarily to allow changing selections without losing the stack
1678 pub disable_clearing: bool,
1679}
1680
1681impl SelectSyntaxNodeHistory {
1682 pub fn try_clear(&mut self) {
1683 if !self.disable_clearing {
1684 self.stack.clear();
1685 }
1686 }
1687
1688 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1689 self.stack.push(selection);
1690 }
1691
1692 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1693 self.stack.pop()
1694 }
1695}
1696
1697enum SelectSyntaxNodeScrollBehavior {
1698 CursorTop,
1699 FitSelection,
1700 CursorBottom,
1701}
1702
1703#[derive(Debug)]
1704pub(crate) struct NavigationData {
1705 cursor_anchor: Anchor,
1706 cursor_position: Point,
1707 scroll_anchor: ScrollAnchor,
1708 scroll_top_row: u32,
1709}
1710
1711#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1712pub enum GotoDefinitionKind {
1713 Symbol,
1714 Declaration,
1715 Type,
1716 Implementation,
1717}
1718
1719pub enum FormatTarget {
1720 Buffers(HashSet<Entity<Buffer>>),
1721 Ranges(Vec<Range<MultiBufferPoint>>),
1722}
1723
1724pub(crate) struct FocusedBlock {
1725 id: BlockId,
1726 focus_handle: WeakFocusHandle,
1727}
1728
1729#[derive(Clone, Debug)]
1730enum JumpData {
1731 MultiBufferRow {
1732 row: MultiBufferRow,
1733 line_offset_from_top: u32,
1734 },
1735 MultiBufferPoint {
1736 excerpt_id: ExcerptId,
1737 position: Point,
1738 anchor: text::Anchor,
1739 line_offset_from_top: u32,
1740 },
1741}
1742
1743pub enum MultibufferSelectionMode {
1744 First,
1745 All,
1746}
1747
1748#[derive(Clone, Copy, Debug, Default)]
1749pub struct RewrapOptions {
1750 pub override_language_settings: bool,
1751 pub preserve_existing_whitespace: bool,
1752}
1753
1754impl Editor {
1755 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1756 let buffer = cx.new(|cx| Buffer::local("", cx));
1757 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1758 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1759 }
1760
1761 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1762 let buffer = cx.new(|cx| Buffer::local("", cx));
1763 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1764 Self::new(EditorMode::full(), buffer, None, window, cx)
1765 }
1766
1767 pub fn auto_height(
1768 min_lines: usize,
1769 max_lines: usize,
1770 window: &mut Window,
1771 cx: &mut Context<Self>,
1772 ) -> Self {
1773 let buffer = cx.new(|cx| Buffer::local("", cx));
1774 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1775 Self::new(
1776 EditorMode::AutoHeight {
1777 min_lines,
1778 max_lines: Some(max_lines),
1779 },
1780 buffer,
1781 None,
1782 window,
1783 cx,
1784 )
1785 }
1786
1787 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1788 /// The editor grows as tall as needed to fit its content.
1789 pub fn auto_height_unbounded(
1790 min_lines: usize,
1791 window: &mut Window,
1792 cx: &mut Context<Self>,
1793 ) -> Self {
1794 let buffer = cx.new(|cx| Buffer::local("", cx));
1795 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1796 Self::new(
1797 EditorMode::AutoHeight {
1798 min_lines,
1799 max_lines: None,
1800 },
1801 buffer,
1802 None,
1803 window,
1804 cx,
1805 )
1806 }
1807
1808 pub fn for_buffer(
1809 buffer: Entity<Buffer>,
1810 project: Option<Entity<Project>>,
1811 window: &mut Window,
1812 cx: &mut Context<Self>,
1813 ) -> Self {
1814 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1815 Self::new(EditorMode::full(), buffer, project, window, cx)
1816 }
1817
1818 pub fn for_multibuffer(
1819 buffer: Entity<MultiBuffer>,
1820 project: Option<Entity<Project>>,
1821 window: &mut Window,
1822 cx: &mut Context<Self>,
1823 ) -> Self {
1824 Self::new(EditorMode::full(), buffer, project, window, cx)
1825 }
1826
1827 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1828 let mut clone = Self::new(
1829 self.mode.clone(),
1830 self.buffer.clone(),
1831 self.project.clone(),
1832 window,
1833 cx,
1834 );
1835 self.display_map.update(cx, |display_map, cx| {
1836 let snapshot = display_map.snapshot(cx);
1837 clone.display_map.update(cx, |display_map, cx| {
1838 display_map.set_state(&snapshot, cx);
1839 });
1840 });
1841 clone.folds_did_change(cx);
1842 clone.selections.clone_state(&self.selections);
1843 clone.scroll_manager.clone_state(&self.scroll_manager);
1844 clone.searchable = self.searchable;
1845 clone.read_only = self.read_only;
1846 clone
1847 }
1848
1849 pub fn new(
1850 mode: EditorMode,
1851 buffer: Entity<MultiBuffer>,
1852 project: Option<Entity<Project>>,
1853 window: &mut Window,
1854 cx: &mut Context<Self>,
1855 ) -> Self {
1856 Editor::new_internal(mode, buffer, project, None, window, cx)
1857 }
1858
1859 pub fn sticky_headers(
1860 &self,
1861 style: &EditorStyle,
1862 cx: &App,
1863 ) -> Option<Vec<OutlineItem<Anchor>>> {
1864 let multi_buffer = self.buffer().read(cx);
1865 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1866 let multi_buffer_visible_start = self
1867 .scroll_manager
1868 .anchor()
1869 .anchor
1870 .to_point(&multi_buffer_snapshot);
1871 let max_row = multi_buffer_snapshot.max_point().row;
1872
1873 let start_row = (multi_buffer_visible_start.row).min(max_row);
1874 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1875
1876 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1877 let outline_items = buffer
1878 .outline_items_containing(
1879 Point::new(start_row, 0)..Point::new(end_row, 0),
1880 true,
1881 Some(style.syntax.as_ref()),
1882 )
1883 .into_iter()
1884 .map(|outline_item| OutlineItem {
1885 depth: outline_item.depth,
1886 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1887 source_range_for_text: Anchor::range_in_buffer(
1888 *excerpt_id,
1889 outline_item.source_range_for_text,
1890 ),
1891 text: outline_item.text,
1892 highlight_ranges: outline_item.highlight_ranges,
1893 name_ranges: outline_item.name_ranges,
1894 body_range: outline_item
1895 .body_range
1896 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1897 annotation_range: outline_item
1898 .annotation_range
1899 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1900 });
1901 return Some(outline_items.collect());
1902 }
1903
1904 None
1905 }
1906
1907 fn new_internal(
1908 mode: EditorMode,
1909 multi_buffer: Entity<MultiBuffer>,
1910 project: Option<Entity<Project>>,
1911 display_map: Option<Entity<DisplayMap>>,
1912 window: &mut Window,
1913 cx: &mut Context<Self>,
1914 ) -> Self {
1915 debug_assert!(
1916 display_map.is_none() || mode.is_minimap(),
1917 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1918 );
1919
1920 let full_mode = mode.is_full();
1921 let is_minimap = mode.is_minimap();
1922 let diagnostics_max_severity = if full_mode {
1923 EditorSettings::get_global(cx)
1924 .diagnostics_max_severity
1925 .unwrap_or(DiagnosticSeverity::Hint)
1926 } else {
1927 DiagnosticSeverity::Off
1928 };
1929 let style = window.text_style();
1930 let font_size = style.font_size.to_pixels(window.rem_size());
1931 let editor = cx.entity().downgrade();
1932 let fold_placeholder = FoldPlaceholder {
1933 constrain_width: false,
1934 render: Arc::new(move |fold_id, fold_range, cx| {
1935 let editor = editor.clone();
1936 div()
1937 .id(fold_id)
1938 .bg(cx.theme().colors().ghost_element_background)
1939 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1940 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1941 .rounded_xs()
1942 .size_full()
1943 .cursor_pointer()
1944 .child("⋯")
1945 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1946 .on_click(move |_, _window, cx| {
1947 editor
1948 .update(cx, |editor, cx| {
1949 editor.unfold_ranges(
1950 &[fold_range.start..fold_range.end],
1951 true,
1952 false,
1953 cx,
1954 );
1955 cx.stop_propagation();
1956 })
1957 .ok();
1958 })
1959 .into_any()
1960 }),
1961 merge_adjacent: true,
1962 ..FoldPlaceholder::default()
1963 };
1964 let display_map = display_map.unwrap_or_else(|| {
1965 cx.new(|cx| {
1966 DisplayMap::new(
1967 multi_buffer.clone(),
1968 style.font(),
1969 font_size,
1970 None,
1971 FILE_HEADER_HEIGHT,
1972 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1973 fold_placeholder,
1974 diagnostics_max_severity,
1975 cx,
1976 )
1977 })
1978 });
1979
1980 let selections = SelectionsCollection::new();
1981
1982 let blink_manager = cx.new(|cx| {
1983 let mut blink_manager = BlinkManager::new(
1984 CURSOR_BLINK_INTERVAL,
1985 |cx| EditorSettings::get_global(cx).cursor_blink,
1986 cx,
1987 );
1988 if is_minimap {
1989 blink_manager.disable(cx);
1990 }
1991 blink_manager
1992 });
1993
1994 let soft_wrap_mode_override =
1995 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1996
1997 let mut project_subscriptions = Vec::new();
1998 if full_mode && let Some(project) = project.as_ref() {
1999 project_subscriptions.push(cx.subscribe_in(
2000 project,
2001 window,
2002 |editor, _, event, window, cx| match event {
2003 project::Event::RefreshCodeLens => {
2004 // we always query lens with actions, without storing them, always refreshing them
2005 }
2006 project::Event::RefreshInlayHints {
2007 server_id,
2008 request_id,
2009 } => {
2010 editor.refresh_inlay_hints(
2011 InlayHintRefreshReason::RefreshRequested {
2012 server_id: *server_id,
2013 request_id: *request_id,
2014 },
2015 cx,
2016 );
2017 }
2018 project::Event::LanguageServerRemoved(..) => {
2019 if editor.tasks_update_task.is_none() {
2020 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2021 }
2022 editor.registered_buffers.clear();
2023 editor.register_visible_buffers(cx);
2024 }
2025 project::Event::LanguageServerAdded(..) => {
2026 if editor.tasks_update_task.is_none() {
2027 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2028 }
2029 }
2030 project::Event::SnippetEdit(id, snippet_edits) => {
2031 // todo(lw): Non singletons
2032 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2033 let snapshot = buffer.read(cx).snapshot();
2034 let focus_handle = editor.focus_handle(cx);
2035 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2036 for (range, snippet) in snippet_edits {
2037 let buffer_range =
2038 language::range_from_lsp(*range).to_offset(&snapshot);
2039 editor
2040 .insert_snippet(
2041 &[MultiBufferOffset(buffer_range.start)
2042 ..MultiBufferOffset(buffer_range.end)],
2043 snippet.clone(),
2044 window,
2045 cx,
2046 )
2047 .ok();
2048 }
2049 }
2050 }
2051 }
2052 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2053 let buffer_id = *buffer_id;
2054 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2055 editor.register_buffer(buffer_id, cx);
2056 editor.update_lsp_data(Some(buffer_id), window, cx);
2057 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2058 refresh_linked_ranges(editor, window, cx);
2059 editor.refresh_code_actions(window, cx);
2060 editor.refresh_document_highlights(cx);
2061 }
2062 }
2063
2064 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2065 let Some(workspace) = editor.workspace() else {
2066 return;
2067 };
2068 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2069 else {
2070 return;
2071 };
2072
2073 if active_editor.entity_id() == cx.entity_id() {
2074 let entity_id = cx.entity_id();
2075 workspace.update(cx, |this, cx| {
2076 this.panes_mut()
2077 .iter_mut()
2078 .filter(|pane| pane.entity_id() != entity_id)
2079 .for_each(|p| {
2080 p.update(cx, |pane, _| {
2081 pane.nav_history_mut().rename_item(
2082 entity_id,
2083 project_path.clone(),
2084 abs_path.clone().into(),
2085 );
2086 })
2087 });
2088 });
2089
2090 Self::open_transaction_for_hidden_buffers(
2091 workspace,
2092 transaction.clone(),
2093 "Rename".to_string(),
2094 window,
2095 cx,
2096 );
2097 }
2098 }
2099
2100 project::Event::WorkspaceEditApplied(transaction) => {
2101 let Some(workspace) = editor.workspace() else {
2102 return;
2103 };
2104 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2105 else {
2106 return;
2107 };
2108
2109 if active_editor.entity_id() == cx.entity_id() {
2110 Self::open_transaction_for_hidden_buffers(
2111 workspace,
2112 transaction.clone(),
2113 "LSP Edit".to_string(),
2114 window,
2115 cx,
2116 );
2117 }
2118 }
2119
2120 _ => {}
2121 },
2122 ));
2123 if let Some(task_inventory) = project
2124 .read(cx)
2125 .task_store()
2126 .read(cx)
2127 .task_inventory()
2128 .cloned()
2129 {
2130 project_subscriptions.push(cx.observe_in(
2131 &task_inventory,
2132 window,
2133 |editor, _, window, cx| {
2134 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2135 },
2136 ));
2137 };
2138
2139 project_subscriptions.push(cx.subscribe_in(
2140 &project.read(cx).breakpoint_store(),
2141 window,
2142 |editor, _, event, window, cx| match event {
2143 BreakpointStoreEvent::ClearDebugLines => {
2144 editor.clear_row_highlights::<ActiveDebugLine>();
2145 editor.refresh_inline_values(cx);
2146 }
2147 BreakpointStoreEvent::SetDebugLine => {
2148 if editor.go_to_active_debug_line(window, cx) {
2149 cx.stop_propagation();
2150 }
2151
2152 editor.refresh_inline_values(cx);
2153 }
2154 _ => {}
2155 },
2156 ));
2157 let git_store = project.read(cx).git_store().clone();
2158 let project = project.clone();
2159 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2160 if let GitStoreEvent::RepositoryAdded = event {
2161 this.load_diff_task = Some(
2162 update_uncommitted_diff_for_buffer(
2163 cx.entity(),
2164 &project,
2165 this.buffer.read(cx).all_buffers(),
2166 this.buffer.clone(),
2167 cx,
2168 )
2169 .shared(),
2170 );
2171 }
2172 }));
2173 }
2174
2175 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2176
2177 let inlay_hint_settings =
2178 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2179 let focus_handle = cx.focus_handle();
2180 if !is_minimap {
2181 cx.on_focus(&focus_handle, window, Self::handle_focus)
2182 .detach();
2183 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2184 .detach();
2185 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2186 .detach();
2187 cx.on_blur(&focus_handle, window, Self::handle_blur)
2188 .detach();
2189 cx.observe_pending_input(window, Self::observe_pending_input)
2190 .detach();
2191 }
2192
2193 let show_indent_guides =
2194 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2195 Some(false)
2196 } else {
2197 None
2198 };
2199
2200 let breakpoint_store = match (&mode, project.as_ref()) {
2201 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2202 _ => None,
2203 };
2204
2205 let mut code_action_providers = Vec::new();
2206 let mut load_uncommitted_diff = None;
2207 if let Some(project) = project.clone() {
2208 load_uncommitted_diff = Some(
2209 update_uncommitted_diff_for_buffer(
2210 cx.entity(),
2211 &project,
2212 multi_buffer.read(cx).all_buffers(),
2213 multi_buffer.clone(),
2214 cx,
2215 )
2216 .shared(),
2217 );
2218 code_action_providers.push(Rc::new(project) as Rc<_>);
2219 }
2220
2221 let mut editor = Self {
2222 focus_handle,
2223 show_cursor_when_unfocused: false,
2224 last_focused_descendant: None,
2225 buffer: multi_buffer.clone(),
2226 display_map: display_map.clone(),
2227 placeholder_display_map: None,
2228 selections,
2229 scroll_manager: ScrollManager::new(cx),
2230 columnar_selection_state: None,
2231 add_selections_state: None,
2232 select_next_state: None,
2233 select_prev_state: None,
2234 selection_history: SelectionHistory::default(),
2235 defer_selection_effects: false,
2236 deferred_selection_effects_state: None,
2237 autoclose_regions: Vec::new(),
2238 snippet_stack: InvalidationStack::default(),
2239 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2240 ime_transaction: None,
2241 active_diagnostics: ActiveDiagnostic::None,
2242 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2243 inline_diagnostics_update: Task::ready(()),
2244 inline_diagnostics: Vec::new(),
2245 soft_wrap_mode_override,
2246 diagnostics_max_severity,
2247 hard_wrap: None,
2248 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2249 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2250 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2251 project,
2252 blink_manager: blink_manager.clone(),
2253 show_local_selections: true,
2254 show_scrollbars: ScrollbarAxes {
2255 horizontal: full_mode,
2256 vertical: full_mode,
2257 },
2258 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2259 offset_content: !matches!(mode, EditorMode::SingleLine),
2260 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2261 show_gutter: full_mode,
2262 show_line_numbers: (!full_mode).then_some(false),
2263 use_relative_line_numbers: None,
2264 disable_expand_excerpt_buttons: !full_mode,
2265 delegate_expand_excerpts: false,
2266 show_git_diff_gutter: None,
2267 show_code_actions: None,
2268 show_runnables: None,
2269 show_breakpoints: None,
2270 show_diff_review_button: false,
2271 show_wrap_guides: None,
2272 show_indent_guides,
2273 buffers_with_disabled_indent_guides: HashSet::default(),
2274 highlight_order: 0,
2275 highlighted_rows: HashMap::default(),
2276 background_highlights: HashMap::default(),
2277 gutter_highlights: HashMap::default(),
2278 scrollbar_marker_state: ScrollbarMarkerState::default(),
2279 active_indent_guides_state: ActiveIndentGuidesState::default(),
2280 nav_history: None,
2281 context_menu: RefCell::new(None),
2282 context_menu_options: None,
2283 mouse_context_menu: None,
2284 completion_tasks: Vec::new(),
2285 inline_blame_popover: None,
2286 inline_blame_popover_show_task: None,
2287 signature_help_state: SignatureHelpState::default(),
2288 auto_signature_help: None,
2289 find_all_references_task_sources: Vec::new(),
2290 next_completion_id: 0,
2291 next_inlay_id: 0,
2292 code_action_providers,
2293 available_code_actions: None,
2294 code_actions_task: None,
2295 quick_selection_highlight_task: None,
2296 debounced_selection_highlight_task: None,
2297 debounced_selection_highlight_complete: false,
2298 document_highlights_task: None,
2299 linked_editing_range_task: None,
2300 pending_rename: None,
2301 searchable: !is_minimap,
2302 cursor_shape: EditorSettings::get_global(cx)
2303 .cursor_shape
2304 .unwrap_or_default(),
2305 cursor_offset_on_selection: false,
2306 current_line_highlight: None,
2307 autoindent_mode: Some(AutoindentMode::EachLine),
2308 collapse_matches: false,
2309 workspace: None,
2310 input_enabled: !is_minimap,
2311 use_modal_editing: full_mode,
2312 read_only: is_minimap,
2313 use_autoclose: true,
2314 use_auto_surround: true,
2315 auto_replace_emoji_shortcode: false,
2316 jsx_tag_auto_close_enabled_in_any_buffer: false,
2317 leader_id: None,
2318 remote_id: None,
2319 hover_state: HoverState::default(),
2320 pending_mouse_down: None,
2321 prev_pressure_stage: None,
2322 hovered_link_state: None,
2323 edit_prediction_provider: None,
2324 active_edit_prediction: None,
2325 stale_edit_prediction_in_menu: None,
2326 edit_prediction_preview: EditPredictionPreview::Inactive {
2327 released_too_fast: false,
2328 },
2329 inline_diagnostics_enabled: full_mode,
2330 diagnostics_enabled: full_mode,
2331 word_completions_enabled: full_mode,
2332 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2333 gutter_hovered: false,
2334 pixel_position_of_newest_cursor: None,
2335 last_bounds: None,
2336 last_position_map: None,
2337 expect_bounds_change: None,
2338 gutter_dimensions: GutterDimensions::default(),
2339 style: None,
2340 show_cursor_names: false,
2341 hovered_cursors: HashMap::default(),
2342 next_editor_action_id: EditorActionId::default(),
2343 editor_actions: Rc::default(),
2344 edit_predictions_hidden_for_vim_mode: false,
2345 show_edit_predictions_override: None,
2346 show_completions_on_input_override: None,
2347 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2348 edit_prediction_settings: EditPredictionSettings::Disabled,
2349 edit_prediction_indent_conflict: false,
2350 edit_prediction_requires_modifier_in_indent_conflict: true,
2351 custom_context_menu: None,
2352 show_git_blame_gutter: false,
2353 show_git_blame_inline: false,
2354 show_selection_menu: None,
2355 show_git_blame_inline_delay_task: None,
2356 git_blame_inline_enabled: full_mode
2357 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2358 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2359 buffer_serialization: is_minimap.not().then(|| {
2360 BufferSerialization::new(
2361 ProjectSettings::get_global(cx)
2362 .session
2363 .restore_unsaved_buffers,
2364 )
2365 }),
2366 blame: None,
2367 blame_subscription: None,
2368 tasks: BTreeMap::default(),
2369
2370 breakpoint_store,
2371 gutter_breakpoint_indicator: (None, None),
2372 gutter_diff_review_indicator: (None, None),
2373 hovered_diff_hunk_row: None,
2374 _subscriptions: (!is_minimap)
2375 .then(|| {
2376 vec![
2377 cx.observe(&multi_buffer, Self::on_buffer_changed),
2378 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2379 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2380 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2381 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2382 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2383 cx.observe_window_activation(window, |editor, window, cx| {
2384 let active = window.is_window_active();
2385 editor.blink_manager.update(cx, |blink_manager, cx| {
2386 if active {
2387 blink_manager.enable(cx);
2388 } else {
2389 blink_manager.disable(cx);
2390 }
2391 });
2392 if active {
2393 editor.show_mouse_cursor(cx);
2394 }
2395 }),
2396 ]
2397 })
2398 .unwrap_or_default(),
2399 tasks_update_task: None,
2400 pull_diagnostics_task: Task::ready(()),
2401 pull_diagnostics_background_task: Task::ready(()),
2402 colors: None,
2403 refresh_colors_task: Task::ready(()),
2404 inlay_hints: None,
2405 next_color_inlay_id: 0,
2406 post_scroll_update: Task::ready(()),
2407 linked_edit_ranges: Default::default(),
2408 in_project_search: false,
2409 previous_search_ranges: None,
2410 breadcrumb_header: None,
2411 focused_block: None,
2412 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2413 addons: HashMap::default(),
2414 registered_buffers: HashMap::default(),
2415 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2416 selection_mark_mode: false,
2417 toggle_fold_multiple_buffers: Task::ready(()),
2418 serialize_selections: Task::ready(()),
2419 serialize_folds: Task::ready(()),
2420 text_style_refinement: None,
2421 load_diff_task: load_uncommitted_diff,
2422 temporary_diff_override: false,
2423 mouse_cursor_hidden: false,
2424 minimap: None,
2425 hide_mouse_mode: EditorSettings::get_global(cx)
2426 .hide_mouse
2427 .unwrap_or_default(),
2428 change_list: ChangeList::new(),
2429 mode,
2430 selection_drag_state: SelectionDragState::None,
2431 folding_newlines: Task::ready(()),
2432 lookup_key: None,
2433 select_next_is_case_sensitive: None,
2434 applicable_language_settings: HashMap::default(),
2435 accent_data: None,
2436 fetched_tree_sitter_chunks: HashMap::default(),
2437 number_deleted_lines: false,
2438 };
2439
2440 if is_minimap {
2441 return editor;
2442 }
2443
2444 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2445 editor.accent_data = editor.fetch_accent_data(cx);
2446
2447 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2448 editor
2449 ._subscriptions
2450 .push(cx.observe(breakpoints, |_, _, cx| {
2451 cx.notify();
2452 }));
2453 }
2454 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2455 editor._subscriptions.extend(project_subscriptions);
2456
2457 editor._subscriptions.push(cx.subscribe_in(
2458 &cx.entity(),
2459 window,
2460 |editor, _, e: &EditorEvent, window, cx| match e {
2461 EditorEvent::ScrollPositionChanged { local, .. } => {
2462 if *local {
2463 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2464 editor.inline_blame_popover.take();
2465 let new_anchor = editor.scroll_manager.anchor();
2466 let snapshot = editor.snapshot(window, cx);
2467 editor.update_restoration_data(cx, move |data| {
2468 data.scroll_position = (
2469 new_anchor.top_row(snapshot.buffer_snapshot()),
2470 new_anchor.offset,
2471 );
2472 });
2473
2474 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2475 cx.background_executor()
2476 .timer(Duration::from_millis(50))
2477 .await;
2478 editor
2479 .update_in(cx, |editor, window, cx| {
2480 editor.register_visible_buffers(cx);
2481 editor.refresh_colors_for_visible_range(None, window, cx);
2482 editor.refresh_inlay_hints(
2483 InlayHintRefreshReason::NewLinesShown,
2484 cx,
2485 );
2486 editor.colorize_brackets(false, cx);
2487 })
2488 .ok();
2489 });
2490 }
2491 }
2492 EditorEvent::Edited { .. } => {
2493 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2494 .map(|vim_mode| vim_mode.0)
2495 .unwrap_or(false);
2496 if !vim_mode {
2497 let display_map = editor.display_snapshot(cx);
2498 let selections = editor.selections.all_adjusted_display(&display_map);
2499 let pop_state = editor
2500 .change_list
2501 .last()
2502 .map(|previous| {
2503 previous.len() == selections.len()
2504 && previous.iter().enumerate().all(|(ix, p)| {
2505 p.to_display_point(&display_map).row()
2506 == selections[ix].head().row()
2507 })
2508 })
2509 .unwrap_or(false);
2510 let new_positions = selections
2511 .into_iter()
2512 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2513 .collect();
2514 editor
2515 .change_list
2516 .push_to_change_list(pop_state, new_positions);
2517 }
2518 }
2519 _ => (),
2520 },
2521 ));
2522
2523 if let Some(dap_store) = editor
2524 .project
2525 .as_ref()
2526 .map(|project| project.read(cx).dap_store())
2527 {
2528 let weak_editor = cx.weak_entity();
2529
2530 editor
2531 ._subscriptions
2532 .push(
2533 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2534 let session_entity = cx.entity();
2535 weak_editor
2536 .update(cx, |editor, cx| {
2537 editor._subscriptions.push(
2538 cx.subscribe(&session_entity, Self::on_debug_session_event),
2539 );
2540 })
2541 .ok();
2542 }),
2543 );
2544
2545 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2546 editor
2547 ._subscriptions
2548 .push(cx.subscribe(&session, Self::on_debug_session_event));
2549 }
2550 }
2551
2552 // skip adding the initial selection to selection history
2553 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2554 editor.end_selection(window, cx);
2555 editor.selection_history.mode = SelectionHistoryMode::Normal;
2556
2557 editor.scroll_manager.show_scrollbars(window, cx);
2558 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2559
2560 if full_mode {
2561 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2562 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2563
2564 if editor.git_blame_inline_enabled {
2565 editor.start_git_blame_inline(false, window, cx);
2566 }
2567
2568 editor.go_to_active_debug_line(window, cx);
2569
2570 editor.minimap =
2571 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2572 editor.colors = Some(LspColorData::new(cx));
2573 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2574
2575 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2576 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2577 }
2578 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2579 }
2580
2581 editor
2582 }
2583
2584 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2585 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2586 }
2587
2588 pub fn deploy_mouse_context_menu(
2589 &mut self,
2590 position: gpui::Point<Pixels>,
2591 context_menu: Entity<ContextMenu>,
2592 window: &mut Window,
2593 cx: &mut Context<Self>,
2594 ) {
2595 self.mouse_context_menu = Some(MouseContextMenu::new(
2596 self,
2597 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2598 context_menu,
2599 window,
2600 cx,
2601 ));
2602 }
2603
2604 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2605 self.mouse_context_menu
2606 .as_ref()
2607 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2608 }
2609
2610 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2611 if self
2612 .selections
2613 .pending_anchor()
2614 .is_some_and(|pending_selection| {
2615 let snapshot = self.buffer().read(cx).snapshot(cx);
2616 pending_selection.range().includes(range, &snapshot)
2617 })
2618 {
2619 return true;
2620 }
2621
2622 self.selections
2623 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2624 .into_iter()
2625 .any(|selection| {
2626 // This is needed to cover a corner case, if we just check for an existing
2627 // selection in the fold range, having a cursor at the start of the fold
2628 // marks it as selected. Non-empty selections don't cause this.
2629 let length = selection.end - selection.start;
2630 length > 0
2631 })
2632 }
2633
2634 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2635 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2636 }
2637
2638 fn key_context_internal(
2639 &self,
2640 has_active_edit_prediction: bool,
2641 window: &mut Window,
2642 cx: &mut App,
2643 ) -> KeyContext {
2644 let mut key_context = KeyContext::new_with_defaults();
2645 key_context.add("Editor");
2646 let mode = match self.mode {
2647 EditorMode::SingleLine => "single_line",
2648 EditorMode::AutoHeight { .. } => "auto_height",
2649 EditorMode::Minimap { .. } => "minimap",
2650 EditorMode::Full { .. } => "full",
2651 };
2652
2653 if EditorSettings::jupyter_enabled(cx) {
2654 key_context.add("jupyter");
2655 }
2656
2657 key_context.set("mode", mode);
2658 if self.pending_rename.is_some() {
2659 key_context.add("renaming");
2660 }
2661
2662 if let Some(snippet_stack) = self.snippet_stack.last() {
2663 key_context.add("in_snippet");
2664
2665 if snippet_stack.active_index > 0 {
2666 key_context.add("has_previous_tabstop");
2667 }
2668
2669 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2670 key_context.add("has_next_tabstop");
2671 }
2672 }
2673
2674 match self.context_menu.borrow().as_ref() {
2675 Some(CodeContextMenu::Completions(menu)) => {
2676 if menu.visible() {
2677 key_context.add("menu");
2678 key_context.add("showing_completions");
2679 }
2680 }
2681 Some(CodeContextMenu::CodeActions(menu)) => {
2682 if menu.visible() {
2683 key_context.add("menu");
2684 key_context.add("showing_code_actions")
2685 }
2686 }
2687 None => {}
2688 }
2689
2690 if self.signature_help_state.has_multiple_signatures() {
2691 key_context.add("showing_signature_help");
2692 }
2693
2694 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2695 if !self.focus_handle(cx).contains_focused(window, cx)
2696 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2697 {
2698 for addon in self.addons.values() {
2699 addon.extend_key_context(&mut key_context, cx)
2700 }
2701 }
2702
2703 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2704 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2705 Some(
2706 file.full_path(cx)
2707 .extension()?
2708 .to_string_lossy()
2709 .to_lowercase(),
2710 )
2711 }) {
2712 key_context.set("extension", extension);
2713 }
2714 } else {
2715 key_context.add("multibuffer");
2716 }
2717
2718 if has_active_edit_prediction {
2719 if self.edit_prediction_in_conflict() {
2720 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2721 } else {
2722 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2723 key_context.add("copilot_suggestion");
2724 }
2725 }
2726
2727 if self.selection_mark_mode {
2728 key_context.add("selection_mode");
2729 }
2730
2731 let disjoint = self.selections.disjoint_anchors();
2732 let snapshot = self.snapshot(window, cx);
2733 let snapshot = snapshot.buffer_snapshot();
2734 if self.mode == EditorMode::SingleLine
2735 && let [selection] = disjoint
2736 && selection.start == selection.end
2737 && selection.end.to_offset(snapshot) == snapshot.len()
2738 {
2739 key_context.add("end_of_input");
2740 }
2741
2742 if self.has_any_expanded_diff_hunks(cx) {
2743 key_context.add("diffs_expanded");
2744 }
2745
2746 key_context
2747 }
2748
2749 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2750 self.last_bounds.as_ref()
2751 }
2752
2753 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2754 if self.mouse_cursor_hidden {
2755 self.mouse_cursor_hidden = false;
2756 cx.notify();
2757 }
2758 }
2759
2760 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2761 let hide_mouse_cursor = match origin {
2762 HideMouseCursorOrigin::TypingAction => {
2763 matches!(
2764 self.hide_mouse_mode,
2765 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2766 )
2767 }
2768 HideMouseCursorOrigin::MovementAction => {
2769 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2770 }
2771 };
2772 if self.mouse_cursor_hidden != hide_mouse_cursor {
2773 self.mouse_cursor_hidden = hide_mouse_cursor;
2774 cx.notify();
2775 }
2776 }
2777
2778 pub fn edit_prediction_in_conflict(&self) -> bool {
2779 if !self.show_edit_predictions_in_menu() {
2780 return false;
2781 }
2782
2783 let showing_completions = self
2784 .context_menu
2785 .borrow()
2786 .as_ref()
2787 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2788
2789 showing_completions
2790 || self.edit_prediction_requires_modifier()
2791 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2792 // bindings to insert tab characters.
2793 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2794 }
2795
2796 pub fn accept_edit_prediction_keybind(
2797 &self,
2798 granularity: EditPredictionGranularity,
2799 window: &mut Window,
2800 cx: &mut App,
2801 ) -> AcceptEditPredictionBinding {
2802 let key_context = self.key_context_internal(true, window, cx);
2803 let in_conflict = self.edit_prediction_in_conflict();
2804
2805 let bindings =
2806 match granularity {
2807 EditPredictionGranularity::Word => window
2808 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2809 EditPredictionGranularity::Line => window
2810 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2811 EditPredictionGranularity::Full => {
2812 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2813 }
2814 };
2815
2816 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2817 !in_conflict
2818 || binding
2819 .keystrokes()
2820 .first()
2821 .is_some_and(|keystroke| keystroke.modifiers().modified())
2822 }))
2823 }
2824
2825 pub fn new_file(
2826 workspace: &mut Workspace,
2827 _: &workspace::NewFile,
2828 window: &mut Window,
2829 cx: &mut Context<Workspace>,
2830 ) {
2831 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2832 "Failed to create buffer",
2833 window,
2834 cx,
2835 |e, _, _| match e.error_code() {
2836 ErrorCode::RemoteUpgradeRequired => Some(format!(
2837 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2838 e.error_tag("required").unwrap_or("the latest version")
2839 )),
2840 _ => None,
2841 },
2842 );
2843 }
2844
2845 pub fn new_in_workspace(
2846 workspace: &mut Workspace,
2847 window: &mut Window,
2848 cx: &mut Context<Workspace>,
2849 ) -> Task<Result<Entity<Editor>>> {
2850 let project = workspace.project().clone();
2851 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2852
2853 cx.spawn_in(window, async move |workspace, cx| {
2854 let buffer = create.await?;
2855 workspace.update_in(cx, |workspace, window, cx| {
2856 let editor =
2857 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2858 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2859 editor
2860 })
2861 })
2862 }
2863
2864 fn new_file_vertical(
2865 workspace: &mut Workspace,
2866 _: &workspace::NewFileSplitVertical,
2867 window: &mut Window,
2868 cx: &mut Context<Workspace>,
2869 ) {
2870 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2871 }
2872
2873 fn new_file_horizontal(
2874 workspace: &mut Workspace,
2875 _: &workspace::NewFileSplitHorizontal,
2876 window: &mut Window,
2877 cx: &mut Context<Workspace>,
2878 ) {
2879 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2880 }
2881
2882 fn new_file_split(
2883 workspace: &mut Workspace,
2884 action: &workspace::NewFileSplit,
2885 window: &mut Window,
2886 cx: &mut Context<Workspace>,
2887 ) {
2888 Self::new_file_in_direction(workspace, action.0, window, cx)
2889 }
2890
2891 fn new_file_in_direction(
2892 workspace: &mut Workspace,
2893 direction: SplitDirection,
2894 window: &mut Window,
2895 cx: &mut Context<Workspace>,
2896 ) {
2897 let project = workspace.project().clone();
2898 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2899
2900 cx.spawn_in(window, async move |workspace, cx| {
2901 let buffer = create.await?;
2902 workspace.update_in(cx, move |workspace, window, cx| {
2903 workspace.split_item(
2904 direction,
2905 Box::new(
2906 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2907 ),
2908 window,
2909 cx,
2910 )
2911 })?;
2912 anyhow::Ok(())
2913 })
2914 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2915 match e.error_code() {
2916 ErrorCode::RemoteUpgradeRequired => Some(format!(
2917 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2918 e.error_tag("required").unwrap_or("the latest version")
2919 )),
2920 _ => None,
2921 }
2922 });
2923 }
2924
2925 pub fn leader_id(&self) -> Option<CollaboratorId> {
2926 self.leader_id
2927 }
2928
2929 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2930 &self.buffer
2931 }
2932
2933 pub fn project(&self) -> Option<&Entity<Project>> {
2934 self.project.as_ref()
2935 }
2936
2937 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2938 self.workspace.as_ref()?.0.upgrade()
2939 }
2940
2941 /// Returns the workspace serialization ID if this editor should be serialized.
2942 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2943 self.workspace
2944 .as_ref()
2945 .filter(|_| self.should_serialize_buffer())
2946 .and_then(|workspace| workspace.1)
2947 }
2948
2949 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2950 self.buffer().read(cx).title(cx)
2951 }
2952
2953 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2954 let git_blame_gutter_max_author_length = self
2955 .render_git_blame_gutter(cx)
2956 .then(|| {
2957 if let Some(blame) = self.blame.as_ref() {
2958 let max_author_length =
2959 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2960 Some(max_author_length)
2961 } else {
2962 None
2963 }
2964 })
2965 .flatten();
2966
2967 EditorSnapshot {
2968 mode: self.mode.clone(),
2969 show_gutter: self.show_gutter,
2970 offset_content: self.offset_content,
2971 show_line_numbers: self.show_line_numbers,
2972 number_deleted_lines: self.number_deleted_lines,
2973 show_git_diff_gutter: self.show_git_diff_gutter,
2974 show_code_actions: self.show_code_actions,
2975 show_runnables: self.show_runnables,
2976 show_breakpoints: self.show_breakpoints,
2977 git_blame_gutter_max_author_length,
2978 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2979 placeholder_display_snapshot: self
2980 .placeholder_display_map
2981 .as_ref()
2982 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2983 scroll_anchor: self.scroll_manager.anchor(),
2984 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2985 is_focused: self.focus_handle.is_focused(window),
2986 current_line_highlight: self
2987 .current_line_highlight
2988 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2989 gutter_hovered: self.gutter_hovered,
2990 }
2991 }
2992
2993 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2994 self.buffer.read(cx).language_at(point, cx)
2995 }
2996
2997 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2998 self.buffer.read(cx).read(cx).file_at(point).cloned()
2999 }
3000
3001 pub fn active_excerpt(
3002 &self,
3003 cx: &App,
3004 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3005 self.buffer
3006 .read(cx)
3007 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3008 }
3009
3010 pub fn mode(&self) -> &EditorMode {
3011 &self.mode
3012 }
3013
3014 pub fn set_mode(&mut self, mode: EditorMode) {
3015 self.mode = mode;
3016 }
3017
3018 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3019 self.collaboration_hub.as_deref()
3020 }
3021
3022 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3023 self.collaboration_hub = Some(hub);
3024 }
3025
3026 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3027 self.in_project_search = in_project_search;
3028 }
3029
3030 pub fn set_custom_context_menu(
3031 &mut self,
3032 f: impl 'static
3033 + Fn(
3034 &mut Self,
3035 DisplayPoint,
3036 &mut Window,
3037 &mut Context<Self>,
3038 ) -> Option<Entity<ui::ContextMenu>>,
3039 ) {
3040 self.custom_context_menu = Some(Box::new(f))
3041 }
3042
3043 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3044 self.completion_provider = provider;
3045 }
3046
3047 #[cfg(any(test, feature = "test-support"))]
3048 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3049 self.completion_provider.clone()
3050 }
3051
3052 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3053 self.semantics_provider.clone()
3054 }
3055
3056 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3057 self.semantics_provider = provider;
3058 }
3059
3060 pub fn set_edit_prediction_provider<T>(
3061 &mut self,
3062 provider: Option<Entity<T>>,
3063 window: &mut Window,
3064 cx: &mut Context<Self>,
3065 ) where
3066 T: EditPredictionDelegate,
3067 {
3068 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3069 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3070 if this.focus_handle.is_focused(window) {
3071 this.update_visible_edit_prediction(window, cx);
3072 }
3073 }),
3074 provider: Arc::new(provider),
3075 });
3076 self.update_edit_prediction_settings(cx);
3077 self.refresh_edit_prediction(false, false, window, cx);
3078 }
3079
3080 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3081 self.placeholder_display_map
3082 .as_ref()
3083 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3084 }
3085
3086 pub fn set_placeholder_text(
3087 &mut self,
3088 placeholder_text: &str,
3089 window: &mut Window,
3090 cx: &mut Context<Self>,
3091 ) {
3092 let multibuffer = cx
3093 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3094
3095 let style = window.text_style();
3096
3097 self.placeholder_display_map = Some(cx.new(|cx| {
3098 DisplayMap::new(
3099 multibuffer,
3100 style.font(),
3101 style.font_size.to_pixels(window.rem_size()),
3102 None,
3103 FILE_HEADER_HEIGHT,
3104 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3105 Default::default(),
3106 DiagnosticSeverity::Off,
3107 cx,
3108 )
3109 }));
3110 cx.notify();
3111 }
3112
3113 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3114 self.cursor_shape = cursor_shape;
3115
3116 // Disrupt blink for immediate user feedback that the cursor shape has changed
3117 self.blink_manager.update(cx, BlinkManager::show_cursor);
3118
3119 cx.notify();
3120 }
3121
3122 pub fn cursor_shape(&self) -> CursorShape {
3123 self.cursor_shape
3124 }
3125
3126 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3127 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3128 }
3129
3130 pub fn set_current_line_highlight(
3131 &mut self,
3132 current_line_highlight: Option<CurrentLineHighlight>,
3133 ) {
3134 self.current_line_highlight = current_line_highlight;
3135 }
3136
3137 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3138 self.collapse_matches = collapse_matches;
3139 }
3140
3141 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3142 if self.collapse_matches {
3143 return range.start..range.start;
3144 }
3145 range.clone()
3146 }
3147
3148 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3149 self.display_map.read(cx).clip_at_line_ends
3150 }
3151
3152 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3153 if self.display_map.read(cx).clip_at_line_ends != clip {
3154 self.display_map
3155 .update(cx, |map, _| map.clip_at_line_ends = clip);
3156 }
3157 }
3158
3159 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3160 self.input_enabled = input_enabled;
3161 }
3162
3163 pub fn set_edit_predictions_hidden_for_vim_mode(
3164 &mut self,
3165 hidden: bool,
3166 window: &mut Window,
3167 cx: &mut Context<Self>,
3168 ) {
3169 if hidden != self.edit_predictions_hidden_for_vim_mode {
3170 self.edit_predictions_hidden_for_vim_mode = hidden;
3171 if hidden {
3172 self.update_visible_edit_prediction(window, cx);
3173 } else {
3174 self.refresh_edit_prediction(true, false, window, cx);
3175 }
3176 }
3177 }
3178
3179 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3180 self.menu_edit_predictions_policy = value;
3181 }
3182
3183 pub fn set_autoindent(&mut self, autoindent: bool) {
3184 if autoindent {
3185 self.autoindent_mode = Some(AutoindentMode::EachLine);
3186 } else {
3187 self.autoindent_mode = None;
3188 }
3189 }
3190
3191 pub fn capability(&self, cx: &App) -> Capability {
3192 if self.read_only {
3193 Capability::ReadOnly
3194 } else {
3195 self.buffer.read(cx).capability()
3196 }
3197 }
3198
3199 pub fn read_only(&self, cx: &App) -> bool {
3200 self.read_only || self.buffer.read(cx).read_only()
3201 }
3202
3203 pub fn set_read_only(&mut self, read_only: bool) {
3204 self.read_only = read_only;
3205 }
3206
3207 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3208 self.use_autoclose = autoclose;
3209 }
3210
3211 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3212 self.use_auto_surround = auto_surround;
3213 }
3214
3215 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3216 self.auto_replace_emoji_shortcode = auto_replace;
3217 }
3218
3219 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3220 self.buffer_serialization = should_serialize.then(|| {
3221 BufferSerialization::new(
3222 ProjectSettings::get_global(cx)
3223 .session
3224 .restore_unsaved_buffers,
3225 )
3226 })
3227 }
3228
3229 fn should_serialize_buffer(&self) -> bool {
3230 self.buffer_serialization.is_some()
3231 }
3232
3233 pub fn toggle_edit_predictions(
3234 &mut self,
3235 _: &ToggleEditPrediction,
3236 window: &mut Window,
3237 cx: &mut Context<Self>,
3238 ) {
3239 if self.show_edit_predictions_override.is_some() {
3240 self.set_show_edit_predictions(None, window, cx);
3241 } else {
3242 let show_edit_predictions = !self.edit_predictions_enabled();
3243 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3244 }
3245 }
3246
3247 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3248 self.show_completions_on_input_override = show_completions_on_input;
3249 }
3250
3251 pub fn set_show_edit_predictions(
3252 &mut self,
3253 show_edit_predictions: Option<bool>,
3254 window: &mut Window,
3255 cx: &mut Context<Self>,
3256 ) {
3257 self.show_edit_predictions_override = show_edit_predictions;
3258 self.update_edit_prediction_settings(cx);
3259
3260 if let Some(false) = show_edit_predictions {
3261 self.discard_edit_prediction(false, cx);
3262 } else {
3263 self.refresh_edit_prediction(false, true, window, cx);
3264 }
3265 }
3266
3267 fn edit_predictions_disabled_in_scope(
3268 &self,
3269 buffer: &Entity<Buffer>,
3270 buffer_position: language::Anchor,
3271 cx: &App,
3272 ) -> bool {
3273 let snapshot = buffer.read(cx).snapshot();
3274 let settings = snapshot.settings_at(buffer_position, cx);
3275
3276 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3277 return false;
3278 };
3279
3280 scope.override_name().is_some_and(|scope_name| {
3281 settings
3282 .edit_predictions_disabled_in
3283 .iter()
3284 .any(|s| s == scope_name)
3285 })
3286 }
3287
3288 pub fn set_use_modal_editing(&mut self, to: bool) {
3289 self.use_modal_editing = to;
3290 }
3291
3292 pub fn use_modal_editing(&self) -> bool {
3293 self.use_modal_editing
3294 }
3295
3296 fn selections_did_change(
3297 &mut self,
3298 local: bool,
3299 old_cursor_position: &Anchor,
3300 effects: SelectionEffects,
3301 window: &mut Window,
3302 cx: &mut Context<Self>,
3303 ) {
3304 window.invalidate_character_coordinates();
3305
3306 // Copy selections to primary selection buffer
3307 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3308 if local {
3309 let selections = self
3310 .selections
3311 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3312 let buffer_handle = self.buffer.read(cx).read(cx);
3313
3314 let mut text = String::new();
3315 for (index, selection) in selections.iter().enumerate() {
3316 let text_for_selection = buffer_handle
3317 .text_for_range(selection.start..selection.end)
3318 .collect::<String>();
3319
3320 text.push_str(&text_for_selection);
3321 if index != selections.len() - 1 {
3322 text.push('\n');
3323 }
3324 }
3325
3326 if !text.is_empty() {
3327 cx.write_to_primary(ClipboardItem::new_string(text));
3328 }
3329 }
3330
3331 let selection_anchors = self.selections.disjoint_anchors_arc();
3332
3333 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3334 self.buffer.update(cx, |buffer, cx| {
3335 buffer.set_active_selections(
3336 &selection_anchors,
3337 self.selections.line_mode(),
3338 self.cursor_shape,
3339 cx,
3340 )
3341 });
3342 }
3343 let display_map = self
3344 .display_map
3345 .update(cx, |display_map, cx| display_map.snapshot(cx));
3346 let buffer = display_map.buffer_snapshot();
3347 if self.selections.count() == 1 {
3348 self.add_selections_state = None;
3349 }
3350 self.select_next_state = None;
3351 self.select_prev_state = None;
3352 self.select_syntax_node_history.try_clear();
3353 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3354 self.snippet_stack.invalidate(&selection_anchors, buffer);
3355 self.take_rename(false, window, cx);
3356
3357 let newest_selection = self.selections.newest_anchor();
3358 let new_cursor_position = newest_selection.head();
3359 let selection_start = newest_selection.start;
3360
3361 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3362 self.push_to_nav_history(
3363 *old_cursor_position,
3364 Some(new_cursor_position.to_point(buffer)),
3365 false,
3366 effects.nav_history == Some(true),
3367 cx,
3368 );
3369 }
3370
3371 if local {
3372 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3373 self.register_buffer(buffer_id, cx);
3374 }
3375
3376 let mut context_menu = self.context_menu.borrow_mut();
3377 let completion_menu = match context_menu.as_ref() {
3378 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3379 Some(CodeContextMenu::CodeActions(_)) => {
3380 *context_menu = None;
3381 None
3382 }
3383 None => None,
3384 };
3385 let completion_position = completion_menu.map(|menu| menu.initial_position);
3386 drop(context_menu);
3387
3388 if effects.completions
3389 && let Some(completion_position) = completion_position
3390 {
3391 let start_offset = selection_start.to_offset(buffer);
3392 let position_matches = start_offset == completion_position.to_offset(buffer);
3393 let continue_showing = if let Some((snap, ..)) =
3394 buffer.point_to_buffer_offset(completion_position)
3395 && !snap.capability.editable()
3396 {
3397 false
3398 } else if position_matches {
3399 if self.snippet_stack.is_empty() {
3400 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3401 == Some(CharKind::Word)
3402 } else {
3403 // Snippet choices can be shown even when the cursor is in whitespace.
3404 // Dismissing the menu with actions like backspace is handled by
3405 // invalidation regions.
3406 true
3407 }
3408 } else {
3409 false
3410 };
3411
3412 if continue_showing {
3413 self.open_or_update_completions_menu(None, None, false, window, cx);
3414 } else {
3415 self.hide_context_menu(window, cx);
3416 }
3417 }
3418
3419 hide_hover(self, cx);
3420
3421 if old_cursor_position.to_display_point(&display_map).row()
3422 != new_cursor_position.to_display_point(&display_map).row()
3423 {
3424 self.available_code_actions.take();
3425 }
3426 self.refresh_code_actions(window, cx);
3427 self.refresh_document_highlights(cx);
3428 refresh_linked_ranges(self, window, cx);
3429
3430 self.refresh_selected_text_highlights(false, window, cx);
3431 self.refresh_matching_bracket_highlights(window, cx);
3432 self.update_visible_edit_prediction(window, cx);
3433 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3434 self.inline_blame_popover.take();
3435 if self.git_blame_inline_enabled {
3436 self.start_inline_blame_timer(window, cx);
3437 }
3438 }
3439
3440 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3441 cx.emit(EditorEvent::SelectionsChanged { local });
3442
3443 let selections = &self.selections.disjoint_anchors_arc();
3444 if selections.len() == 1 {
3445 cx.emit(SearchEvent::ActiveMatchChanged)
3446 }
3447 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3448 let inmemory_selections = selections
3449 .iter()
3450 .map(|s| {
3451 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3452 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3453 })
3454 .collect();
3455 self.update_restoration_data(cx, |data| {
3456 data.selections = inmemory_selections;
3457 });
3458
3459 if WorkspaceSettings::get(None, cx).restore_on_startup
3460 != RestoreOnStartupBehavior::EmptyTab
3461 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3462 {
3463 let snapshot = self.buffer().read(cx).snapshot(cx);
3464 let selections = selections.clone();
3465 let background_executor = cx.background_executor().clone();
3466 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3467 self.serialize_selections = cx.background_spawn(async move {
3468 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3469 let db_selections = selections
3470 .iter()
3471 .map(|selection| {
3472 (
3473 selection.start.to_offset(&snapshot).0,
3474 selection.end.to_offset(&snapshot).0,
3475 )
3476 })
3477 .collect();
3478
3479 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3480 .await
3481 .with_context(|| {
3482 format!(
3483 "persisting editor selections for editor {editor_id}, \
3484 workspace {workspace_id:?}"
3485 )
3486 })
3487 .log_err();
3488 });
3489 }
3490 }
3491
3492 cx.notify();
3493 }
3494
3495 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3496 use text::ToOffset as _;
3497 use text::ToPoint as _;
3498
3499 if self.mode.is_minimap()
3500 || WorkspaceSettings::get(None, cx).restore_on_startup
3501 == RestoreOnStartupBehavior::EmptyTab
3502 {
3503 return;
3504 }
3505
3506 if !self.buffer().read(cx).is_singleton() {
3507 return;
3508 }
3509
3510 let display_snapshot = self
3511 .display_map
3512 .update(cx, |display_map, cx| display_map.snapshot(cx));
3513 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3514 return;
3515 };
3516 let inmemory_folds = display_snapshot
3517 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3518 .map(|fold| {
3519 fold.range.start.text_anchor.to_point(&snapshot)
3520 ..fold.range.end.text_anchor.to_point(&snapshot)
3521 })
3522 .collect();
3523 self.update_restoration_data(cx, |data| {
3524 data.folds = inmemory_folds;
3525 });
3526
3527 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3528 return;
3529 };
3530 let background_executor = cx.background_executor().clone();
3531 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3532 const FINGERPRINT_LEN: usize = 32;
3533 let db_folds = display_snapshot
3534 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3535 .map(|fold| {
3536 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3537 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3538
3539 // Extract fingerprints - content at fold boundaries for validation on restore
3540 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3541 // content that might change independently.
3542 // start_fp: first min(32, fold_len) bytes of fold content
3543 // end_fp: last min(32, fold_len) bytes of fold content
3544 // Clip to character boundaries to handle multibyte UTF-8 characters.
3545 let fold_len = end - start;
3546 let start_fp_end = snapshot
3547 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3548 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3549 let end_fp_start = snapshot
3550 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3551 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3552
3553 (start, end, start_fp, end_fp)
3554 })
3555 .collect::<Vec<_>>();
3556 self.serialize_folds = cx.background_spawn(async move {
3557 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3558 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3559 .await
3560 .with_context(|| {
3561 format!(
3562 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3563 )
3564 })
3565 .log_err();
3566 });
3567 }
3568
3569 pub fn sync_selections(
3570 &mut self,
3571 other: Entity<Editor>,
3572 cx: &mut Context<Self>,
3573 ) -> gpui::Subscription {
3574 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3575 if !other_selections.is_empty() {
3576 self.selections
3577 .change_with(&self.display_snapshot(cx), |selections| {
3578 selections.select_anchors(other_selections);
3579 });
3580 }
3581
3582 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3583 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3584 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3585 if other_selections.is_empty() {
3586 return;
3587 }
3588 let snapshot = this.display_snapshot(cx);
3589 this.selections.change_with(&snapshot, |selections| {
3590 selections.select_anchors(other_selections);
3591 });
3592 }
3593 });
3594
3595 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3596 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3597 let these_selections = this.selections.disjoint_anchors().to_vec();
3598 if these_selections.is_empty() {
3599 return;
3600 }
3601 other.update(cx, |other_editor, cx| {
3602 let snapshot = other_editor.display_snapshot(cx);
3603 other_editor
3604 .selections
3605 .change_with(&snapshot, |selections| {
3606 selections.select_anchors(these_selections);
3607 })
3608 });
3609 }
3610 });
3611
3612 Subscription::join(other_subscription, this_subscription)
3613 }
3614
3615 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3616 if self.buffer().read(cx).is_singleton() {
3617 return;
3618 }
3619 let snapshot = self.buffer.read(cx).snapshot(cx);
3620 let buffer_ids: HashSet<BufferId> = self
3621 .selections
3622 .disjoint_anchor_ranges()
3623 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3624 .collect();
3625 for buffer_id in buffer_ids {
3626 self.unfold_buffer(buffer_id, cx);
3627 }
3628 }
3629
3630 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3631 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3632 /// effects of selection change occur at the end of the transaction.
3633 pub fn change_selections<R>(
3634 &mut self,
3635 effects: SelectionEffects,
3636 window: &mut Window,
3637 cx: &mut Context<Self>,
3638 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3639 ) -> R {
3640 let snapshot = self.display_snapshot(cx);
3641 if let Some(state) = &mut self.deferred_selection_effects_state {
3642 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3643 state.effects.completions = effects.completions;
3644 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3645 let (changed, result) = self.selections.change_with(&snapshot, change);
3646 state.changed |= changed;
3647 return result;
3648 }
3649 let mut state = DeferredSelectionEffectsState {
3650 changed: false,
3651 effects,
3652 old_cursor_position: self.selections.newest_anchor().head(),
3653 history_entry: SelectionHistoryEntry {
3654 selections: self.selections.disjoint_anchors_arc(),
3655 select_next_state: self.select_next_state.clone(),
3656 select_prev_state: self.select_prev_state.clone(),
3657 add_selections_state: self.add_selections_state.clone(),
3658 },
3659 };
3660 let (changed, result) = self.selections.change_with(&snapshot, change);
3661 state.changed = state.changed || changed;
3662 if self.defer_selection_effects {
3663 self.deferred_selection_effects_state = Some(state);
3664 } else {
3665 self.apply_selection_effects(state, window, cx);
3666 }
3667 result
3668 }
3669
3670 /// Defers the effects of selection change, so that the effects of multiple calls to
3671 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3672 /// to selection history and the state of popovers based on selection position aren't
3673 /// erroneously updated.
3674 pub fn with_selection_effects_deferred<R>(
3675 &mut self,
3676 window: &mut Window,
3677 cx: &mut Context<Self>,
3678 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3679 ) -> R {
3680 let already_deferred = self.defer_selection_effects;
3681 self.defer_selection_effects = true;
3682 let result = update(self, window, cx);
3683 if !already_deferred {
3684 self.defer_selection_effects = false;
3685 if let Some(state) = self.deferred_selection_effects_state.take() {
3686 self.apply_selection_effects(state, window, cx);
3687 }
3688 }
3689 result
3690 }
3691
3692 fn apply_selection_effects(
3693 &mut self,
3694 state: DeferredSelectionEffectsState,
3695 window: &mut Window,
3696 cx: &mut Context<Self>,
3697 ) {
3698 if state.changed {
3699 self.selection_history.push(state.history_entry);
3700
3701 if let Some(autoscroll) = state.effects.scroll {
3702 self.request_autoscroll(autoscroll, cx);
3703 }
3704
3705 let old_cursor_position = &state.old_cursor_position;
3706
3707 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3708
3709 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3710 self.show_signature_help(&ShowSignatureHelp, window, cx);
3711 }
3712 }
3713 }
3714
3715 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3716 where
3717 I: IntoIterator<Item = (Range<S>, T)>,
3718 S: ToOffset,
3719 T: Into<Arc<str>>,
3720 {
3721 if self.read_only(cx) {
3722 return;
3723 }
3724
3725 self.buffer
3726 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3727 }
3728
3729 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3730 where
3731 I: IntoIterator<Item = (Range<S>, T)>,
3732 S: ToOffset,
3733 T: Into<Arc<str>>,
3734 {
3735 if self.read_only(cx) {
3736 return;
3737 }
3738
3739 self.buffer.update(cx, |buffer, cx| {
3740 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3741 });
3742 }
3743
3744 pub fn edit_with_block_indent<I, S, T>(
3745 &mut self,
3746 edits: I,
3747 original_indent_columns: Vec<Option<u32>>,
3748 cx: &mut Context<Self>,
3749 ) where
3750 I: IntoIterator<Item = (Range<S>, T)>,
3751 S: ToOffset,
3752 T: Into<Arc<str>>,
3753 {
3754 if self.read_only(cx) {
3755 return;
3756 }
3757
3758 self.buffer.update(cx, |buffer, cx| {
3759 buffer.edit(
3760 edits,
3761 Some(AutoindentMode::Block {
3762 original_indent_columns,
3763 }),
3764 cx,
3765 )
3766 });
3767 }
3768
3769 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3770 self.hide_context_menu(window, cx);
3771
3772 match phase {
3773 SelectPhase::Begin {
3774 position,
3775 add,
3776 click_count,
3777 } => self.begin_selection(position, add, click_count, window, cx),
3778 SelectPhase::BeginColumnar {
3779 position,
3780 goal_column,
3781 reset,
3782 mode,
3783 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3784 SelectPhase::Extend {
3785 position,
3786 click_count,
3787 } => self.extend_selection(position, click_count, window, cx),
3788 SelectPhase::Update {
3789 position,
3790 goal_column,
3791 scroll_delta,
3792 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3793 SelectPhase::End => self.end_selection(window, cx),
3794 }
3795 }
3796
3797 fn extend_selection(
3798 &mut self,
3799 position: DisplayPoint,
3800 click_count: usize,
3801 window: &mut Window,
3802 cx: &mut Context<Self>,
3803 ) {
3804 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3805 let tail = self
3806 .selections
3807 .newest::<MultiBufferOffset>(&display_map)
3808 .tail();
3809 let click_count = click_count.max(match self.selections.select_mode() {
3810 SelectMode::Character => 1,
3811 SelectMode::Word(_) => 2,
3812 SelectMode::Line(_) => 3,
3813 SelectMode::All => 4,
3814 });
3815 self.begin_selection(position, false, click_count, window, cx);
3816
3817 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3818
3819 let current_selection = match self.selections.select_mode() {
3820 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3821 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3822 };
3823
3824 let mut pending_selection = self
3825 .selections
3826 .pending_anchor()
3827 .cloned()
3828 .expect("extend_selection not called with pending selection");
3829
3830 if pending_selection
3831 .start
3832 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3833 == Ordering::Greater
3834 {
3835 pending_selection.start = current_selection.start;
3836 }
3837 if pending_selection
3838 .end
3839 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3840 == Ordering::Less
3841 {
3842 pending_selection.end = current_selection.end;
3843 pending_selection.reversed = true;
3844 }
3845
3846 let mut pending_mode = self.selections.pending_mode().unwrap();
3847 match &mut pending_mode {
3848 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3849 _ => {}
3850 }
3851
3852 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3853 SelectionEffects::scroll(Autoscroll::fit())
3854 } else {
3855 SelectionEffects::no_scroll()
3856 };
3857
3858 self.change_selections(effects, window, cx, |s| {
3859 s.set_pending(pending_selection.clone(), pending_mode);
3860 s.set_is_extending(true);
3861 });
3862 }
3863
3864 fn begin_selection(
3865 &mut self,
3866 position: DisplayPoint,
3867 add: bool,
3868 click_count: usize,
3869 window: &mut Window,
3870 cx: &mut Context<Self>,
3871 ) {
3872 if !self.focus_handle.is_focused(window) {
3873 self.last_focused_descendant = None;
3874 window.focus(&self.focus_handle, cx);
3875 }
3876
3877 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3878 let buffer = display_map.buffer_snapshot();
3879 let position = display_map.clip_point(position, Bias::Left);
3880
3881 let start;
3882 let end;
3883 let mode;
3884 let mut auto_scroll;
3885 match click_count {
3886 1 => {
3887 start = buffer.anchor_before(position.to_point(&display_map));
3888 end = start;
3889 mode = SelectMode::Character;
3890 auto_scroll = true;
3891 }
3892 2 => {
3893 let position = display_map
3894 .clip_point(position, Bias::Left)
3895 .to_offset(&display_map, Bias::Left);
3896 let (range, _) = buffer.surrounding_word(position, None);
3897 start = buffer.anchor_before(range.start);
3898 end = buffer.anchor_before(range.end);
3899 mode = SelectMode::Word(start..end);
3900 auto_scroll = true;
3901 }
3902 3 => {
3903 let position = display_map
3904 .clip_point(position, Bias::Left)
3905 .to_point(&display_map);
3906 let line_start = display_map.prev_line_boundary(position).0;
3907 let next_line_start = buffer.clip_point(
3908 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3909 Bias::Left,
3910 );
3911 start = buffer.anchor_before(line_start);
3912 end = buffer.anchor_before(next_line_start);
3913 mode = SelectMode::Line(start..end);
3914 auto_scroll = true;
3915 }
3916 _ => {
3917 start = buffer.anchor_before(MultiBufferOffset(0));
3918 end = buffer.anchor_before(buffer.len());
3919 mode = SelectMode::All;
3920 auto_scroll = false;
3921 }
3922 }
3923 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3924
3925 let point_to_delete: Option<usize> = {
3926 let selected_points: Vec<Selection<Point>> =
3927 self.selections.disjoint_in_range(start..end, &display_map);
3928
3929 if !add || click_count > 1 {
3930 None
3931 } else if !selected_points.is_empty() {
3932 Some(selected_points[0].id)
3933 } else {
3934 let clicked_point_already_selected =
3935 self.selections.disjoint_anchors().iter().find(|selection| {
3936 selection.start.to_point(buffer) == start.to_point(buffer)
3937 || selection.end.to_point(buffer) == end.to_point(buffer)
3938 });
3939
3940 clicked_point_already_selected.map(|selection| selection.id)
3941 }
3942 };
3943
3944 let selections_count = self.selections.count();
3945 let effects = if auto_scroll {
3946 SelectionEffects::default()
3947 } else {
3948 SelectionEffects::no_scroll()
3949 };
3950
3951 self.change_selections(effects, window, cx, |s| {
3952 if let Some(point_to_delete) = point_to_delete {
3953 s.delete(point_to_delete);
3954
3955 if selections_count == 1 {
3956 s.set_pending_anchor_range(start..end, mode);
3957 }
3958 } else {
3959 if !add {
3960 s.clear_disjoint();
3961 }
3962
3963 s.set_pending_anchor_range(start..end, mode);
3964 }
3965 });
3966 }
3967
3968 fn begin_columnar_selection(
3969 &mut self,
3970 position: DisplayPoint,
3971 goal_column: u32,
3972 reset: bool,
3973 mode: ColumnarMode,
3974 window: &mut Window,
3975 cx: &mut Context<Self>,
3976 ) {
3977 if !self.focus_handle.is_focused(window) {
3978 self.last_focused_descendant = None;
3979 window.focus(&self.focus_handle, cx);
3980 }
3981
3982 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3983
3984 if reset {
3985 let pointer_position = display_map
3986 .buffer_snapshot()
3987 .anchor_before(position.to_point(&display_map));
3988
3989 self.change_selections(
3990 SelectionEffects::scroll(Autoscroll::newest()),
3991 window,
3992 cx,
3993 |s| {
3994 s.clear_disjoint();
3995 s.set_pending_anchor_range(
3996 pointer_position..pointer_position,
3997 SelectMode::Character,
3998 );
3999 },
4000 );
4001 };
4002
4003 let tail = self.selections.newest::<Point>(&display_map).tail();
4004 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4005 self.columnar_selection_state = match mode {
4006 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4007 selection_tail: selection_anchor,
4008 display_point: if reset {
4009 if position.column() != goal_column {
4010 Some(DisplayPoint::new(position.row(), goal_column))
4011 } else {
4012 None
4013 }
4014 } else {
4015 None
4016 },
4017 }),
4018 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4019 selection_tail: selection_anchor,
4020 }),
4021 };
4022
4023 if !reset {
4024 self.select_columns(position, goal_column, &display_map, window, cx);
4025 }
4026 }
4027
4028 fn update_selection(
4029 &mut self,
4030 position: DisplayPoint,
4031 goal_column: u32,
4032 scroll_delta: gpui::Point<f32>,
4033 window: &mut Window,
4034 cx: &mut Context<Self>,
4035 ) {
4036 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4037
4038 if self.columnar_selection_state.is_some() {
4039 self.select_columns(position, goal_column, &display_map, window, cx);
4040 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4041 let buffer = display_map.buffer_snapshot();
4042 let head;
4043 let tail;
4044 let mode = self.selections.pending_mode().unwrap();
4045 match &mode {
4046 SelectMode::Character => {
4047 head = position.to_point(&display_map);
4048 tail = pending.tail().to_point(buffer);
4049 }
4050 SelectMode::Word(original_range) => {
4051 let offset = display_map
4052 .clip_point(position, Bias::Left)
4053 .to_offset(&display_map, Bias::Left);
4054 let original_range = original_range.to_offset(buffer);
4055
4056 let head_offset = if buffer.is_inside_word(offset, None)
4057 || original_range.contains(&offset)
4058 {
4059 let (word_range, _) = buffer.surrounding_word(offset, None);
4060 if word_range.start < original_range.start {
4061 word_range.start
4062 } else {
4063 word_range.end
4064 }
4065 } else {
4066 offset
4067 };
4068
4069 head = head_offset.to_point(buffer);
4070 if head_offset <= original_range.start {
4071 tail = original_range.end.to_point(buffer);
4072 } else {
4073 tail = original_range.start.to_point(buffer);
4074 }
4075 }
4076 SelectMode::Line(original_range) => {
4077 let original_range = original_range.to_point(display_map.buffer_snapshot());
4078
4079 let position = display_map
4080 .clip_point(position, Bias::Left)
4081 .to_point(&display_map);
4082 let line_start = display_map.prev_line_boundary(position).0;
4083 let next_line_start = buffer.clip_point(
4084 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4085 Bias::Left,
4086 );
4087
4088 if line_start < original_range.start {
4089 head = line_start
4090 } else {
4091 head = next_line_start
4092 }
4093
4094 if head <= original_range.start {
4095 tail = original_range.end;
4096 } else {
4097 tail = original_range.start;
4098 }
4099 }
4100 SelectMode::All => {
4101 return;
4102 }
4103 };
4104
4105 if head < tail {
4106 pending.start = buffer.anchor_before(head);
4107 pending.end = buffer.anchor_before(tail);
4108 pending.reversed = true;
4109 } else {
4110 pending.start = buffer.anchor_before(tail);
4111 pending.end = buffer.anchor_before(head);
4112 pending.reversed = false;
4113 }
4114
4115 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4116 s.set_pending(pending.clone(), mode);
4117 });
4118 } else {
4119 log::error!("update_selection dispatched with no pending selection");
4120 return;
4121 }
4122
4123 self.apply_scroll_delta(scroll_delta, window, cx);
4124 cx.notify();
4125 }
4126
4127 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4128 self.columnar_selection_state.take();
4129 if let Some(pending_mode) = self.selections.pending_mode() {
4130 let selections = self
4131 .selections
4132 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4133 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4134 s.select(selections);
4135 s.clear_pending();
4136 if s.is_extending() {
4137 s.set_is_extending(false);
4138 } else {
4139 s.set_select_mode(pending_mode);
4140 }
4141 });
4142 }
4143 }
4144
4145 fn select_columns(
4146 &mut self,
4147 head: DisplayPoint,
4148 goal_column: u32,
4149 display_map: &DisplaySnapshot,
4150 window: &mut Window,
4151 cx: &mut Context<Self>,
4152 ) {
4153 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4154 return;
4155 };
4156
4157 let tail = match columnar_state {
4158 ColumnarSelectionState::FromMouse {
4159 selection_tail,
4160 display_point,
4161 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4162 ColumnarSelectionState::FromSelection { selection_tail } => {
4163 selection_tail.to_display_point(display_map)
4164 }
4165 };
4166
4167 let start_row = cmp::min(tail.row(), head.row());
4168 let end_row = cmp::max(tail.row(), head.row());
4169 let start_column = cmp::min(tail.column(), goal_column);
4170 let end_column = cmp::max(tail.column(), goal_column);
4171 let reversed = start_column < tail.column();
4172
4173 let selection_ranges = (start_row.0..=end_row.0)
4174 .map(DisplayRow)
4175 .filter_map(|row| {
4176 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4177 || start_column <= display_map.line_len(row))
4178 && !display_map.is_block_line(row)
4179 {
4180 let start = display_map
4181 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4182 .to_point(display_map);
4183 let end = display_map
4184 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4185 .to_point(display_map);
4186 if reversed {
4187 Some(end..start)
4188 } else {
4189 Some(start..end)
4190 }
4191 } else {
4192 None
4193 }
4194 })
4195 .collect::<Vec<_>>();
4196 if selection_ranges.is_empty() {
4197 return;
4198 }
4199
4200 let ranges = match columnar_state {
4201 ColumnarSelectionState::FromMouse { .. } => {
4202 let mut non_empty_ranges = selection_ranges
4203 .iter()
4204 .filter(|selection_range| selection_range.start != selection_range.end)
4205 .peekable();
4206 if non_empty_ranges.peek().is_some() {
4207 non_empty_ranges.cloned().collect()
4208 } else {
4209 selection_ranges
4210 }
4211 }
4212 _ => selection_ranges,
4213 };
4214
4215 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4216 s.select_ranges(ranges);
4217 });
4218 cx.notify();
4219 }
4220
4221 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4222 self.selections
4223 .all_adjusted(snapshot)
4224 .iter()
4225 .any(|selection| !selection.is_empty())
4226 }
4227
4228 pub fn has_pending_nonempty_selection(&self) -> bool {
4229 let pending_nonempty_selection = match self.selections.pending_anchor() {
4230 Some(Selection { start, end, .. }) => start != end,
4231 None => false,
4232 };
4233
4234 pending_nonempty_selection
4235 || (self.columnar_selection_state.is_some()
4236 && self.selections.disjoint_anchors().len() > 1)
4237 }
4238
4239 pub fn has_pending_selection(&self) -> bool {
4240 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4241 }
4242
4243 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4244 self.selection_mark_mode = false;
4245 self.selection_drag_state = SelectionDragState::None;
4246
4247 if self.dismiss_menus_and_popups(true, window, cx) {
4248 cx.notify();
4249 return;
4250 }
4251 if self.clear_expanded_diff_hunks(cx) {
4252 cx.notify();
4253 return;
4254 }
4255 if self.show_git_blame_gutter {
4256 self.show_git_blame_gutter = false;
4257 cx.notify();
4258 return;
4259 }
4260
4261 if self.mode.is_full()
4262 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4263 {
4264 cx.notify();
4265 return;
4266 }
4267
4268 cx.propagate();
4269 }
4270
4271 pub fn dismiss_menus_and_popups(
4272 &mut self,
4273 is_user_requested: bool,
4274 window: &mut Window,
4275 cx: &mut Context<Self>,
4276 ) -> bool {
4277 let mut dismissed = false;
4278
4279 dismissed |= self.take_rename(false, window, cx).is_some();
4280 dismissed |= self.hide_blame_popover(true, cx);
4281 dismissed |= hide_hover(self, cx);
4282 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4283 dismissed |= self.hide_context_menu(window, cx).is_some();
4284 dismissed |= self.mouse_context_menu.take().is_some();
4285 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4286 dismissed |= self.snippet_stack.pop().is_some();
4287
4288 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4289 self.dismiss_diagnostics(cx);
4290 dismissed = true;
4291 }
4292
4293 dismissed
4294 }
4295
4296 fn linked_editing_ranges_for(
4297 &self,
4298 selection: Range<text::Anchor>,
4299 cx: &App,
4300 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4301 if self.linked_edit_ranges.is_empty() {
4302 return None;
4303 }
4304 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4305 selection.end.buffer_id.and_then(|end_buffer_id| {
4306 if selection.start.buffer_id != Some(end_buffer_id) {
4307 return None;
4308 }
4309 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4310 let snapshot = buffer.read(cx).snapshot();
4311 self.linked_edit_ranges
4312 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4313 .map(|ranges| (ranges, snapshot, buffer))
4314 })?;
4315 use text::ToOffset as TO;
4316 // find offset from the start of current range to current cursor position
4317 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4318
4319 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4320 let start_difference = start_offset - start_byte_offset;
4321 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4322 let end_difference = end_offset - start_byte_offset;
4323 // Current range has associated linked ranges.
4324 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4325 for range in linked_ranges.iter() {
4326 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4327 let end_offset = start_offset + end_difference;
4328 let start_offset = start_offset + start_difference;
4329 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4330 continue;
4331 }
4332 if self.selections.disjoint_anchor_ranges().any(|s| {
4333 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4334 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4335 {
4336 return false;
4337 }
4338 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4339 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4340 }) {
4341 continue;
4342 }
4343 let start = buffer_snapshot.anchor_after(start_offset);
4344 let end = buffer_snapshot.anchor_after(end_offset);
4345 linked_edits
4346 .entry(buffer.clone())
4347 .or_default()
4348 .push(start..end);
4349 }
4350 Some(linked_edits)
4351 }
4352
4353 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4354 let text: Arc<str> = text.into();
4355
4356 if self.read_only(cx) {
4357 return;
4358 }
4359
4360 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4361
4362 self.unfold_buffers_with_selections(cx);
4363
4364 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4365 let mut bracket_inserted = false;
4366 let mut edits = Vec::new();
4367 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4368 let mut new_selections = Vec::with_capacity(selections.len());
4369 let mut new_autoclose_regions = Vec::new();
4370 let snapshot = self.buffer.read(cx).read(cx);
4371 let mut clear_linked_edit_ranges = false;
4372 let mut all_selections_read_only = true;
4373 let mut has_adjacent_edits = false;
4374 let mut in_adjacent_group = false;
4375
4376 let mut regions = self
4377 .selections_with_autoclose_regions(selections, &snapshot)
4378 .peekable();
4379
4380 while let Some((selection, autoclose_region)) = regions.next() {
4381 if snapshot
4382 .point_to_buffer_point(selection.head())
4383 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4384 {
4385 continue;
4386 }
4387 if snapshot
4388 .point_to_buffer_point(selection.tail())
4389 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4390 {
4391 // note, ideally we'd clip the tail to the closest writeable region towards the head
4392 continue;
4393 }
4394 all_selections_read_only = false;
4395
4396 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4397 // Determine if the inserted text matches the opening or closing
4398 // bracket of any of this language's bracket pairs.
4399 let mut bracket_pair = None;
4400 let mut is_bracket_pair_start = false;
4401 let mut is_bracket_pair_end = false;
4402 if !text.is_empty() {
4403 let mut bracket_pair_matching_end = None;
4404 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4405 // and they are removing the character that triggered IME popup.
4406 for (pair, enabled) in scope.brackets() {
4407 if !pair.close && !pair.surround {
4408 continue;
4409 }
4410
4411 if enabled && pair.start.ends_with(text.as_ref()) {
4412 let prefix_len = pair.start.len() - text.len();
4413 let preceding_text_matches_prefix = prefix_len == 0
4414 || (selection.start.column >= (prefix_len as u32)
4415 && snapshot.contains_str_at(
4416 Point::new(
4417 selection.start.row,
4418 selection.start.column - (prefix_len as u32),
4419 ),
4420 &pair.start[..prefix_len],
4421 ));
4422 if preceding_text_matches_prefix {
4423 bracket_pair = Some(pair.clone());
4424 is_bracket_pair_start = true;
4425 break;
4426 }
4427 }
4428 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4429 {
4430 // take first bracket pair matching end, but don't break in case a later bracket
4431 // pair matches start
4432 bracket_pair_matching_end = Some(pair.clone());
4433 }
4434 }
4435 if let Some(end) = bracket_pair_matching_end
4436 && bracket_pair.is_none()
4437 {
4438 bracket_pair = Some(end);
4439 is_bracket_pair_end = true;
4440 }
4441 }
4442
4443 if let Some(bracket_pair) = bracket_pair {
4444 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4445 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4446 let auto_surround =
4447 self.use_auto_surround && snapshot_settings.use_auto_surround;
4448 if selection.is_empty() {
4449 if is_bracket_pair_start {
4450 // If the inserted text is a suffix of an opening bracket and the
4451 // selection is preceded by the rest of the opening bracket, then
4452 // insert the closing bracket.
4453 let following_text_allows_autoclose = snapshot
4454 .chars_at(selection.start)
4455 .next()
4456 .is_none_or(|c| scope.should_autoclose_before(c));
4457
4458 let preceding_text_allows_autoclose = selection.start.column == 0
4459 || snapshot
4460 .reversed_chars_at(selection.start)
4461 .next()
4462 .is_none_or(|c| {
4463 bracket_pair.start != bracket_pair.end
4464 || !snapshot
4465 .char_classifier_at(selection.start)
4466 .is_word(c)
4467 });
4468
4469 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4470 && bracket_pair.start.len() == 1
4471 {
4472 let target = bracket_pair.start.chars().next().unwrap();
4473 let mut byte_offset = 0u32;
4474 let current_line_count = snapshot
4475 .reversed_chars_at(selection.start)
4476 .take_while(|&c| c != '\n')
4477 .filter(|c| {
4478 byte_offset += c.len_utf8() as u32;
4479 if *c != target {
4480 return false;
4481 }
4482
4483 let point = Point::new(
4484 selection.start.row,
4485 selection.start.column.saturating_sub(byte_offset),
4486 );
4487
4488 let is_enabled = snapshot
4489 .language_scope_at(point)
4490 .and_then(|scope| {
4491 scope
4492 .brackets()
4493 .find(|(pair, _)| {
4494 pair.start == bracket_pair.start
4495 })
4496 .map(|(_, enabled)| enabled)
4497 })
4498 .unwrap_or(true);
4499
4500 let is_delimiter = snapshot
4501 .language_scope_at(Point::new(
4502 point.row,
4503 point.column + 1,
4504 ))
4505 .and_then(|scope| {
4506 scope
4507 .brackets()
4508 .find(|(pair, _)| {
4509 pair.start == bracket_pair.start
4510 })
4511 .map(|(_, enabled)| !enabled)
4512 })
4513 .unwrap_or(false);
4514
4515 is_enabled && !is_delimiter
4516 })
4517 .count();
4518 current_line_count % 2 == 1
4519 } else {
4520 false
4521 };
4522
4523 if autoclose
4524 && bracket_pair.close
4525 && following_text_allows_autoclose
4526 && preceding_text_allows_autoclose
4527 && !is_closing_quote
4528 {
4529 let anchor = snapshot.anchor_before(selection.end);
4530 new_selections.push((selection.map(|_| anchor), text.len()));
4531 new_autoclose_regions.push((
4532 anchor,
4533 text.len(),
4534 selection.id,
4535 bracket_pair.clone(),
4536 ));
4537 edits.push((
4538 selection.range(),
4539 format!("{}{}", text, bracket_pair.end).into(),
4540 ));
4541 bracket_inserted = true;
4542 continue;
4543 }
4544 }
4545
4546 if let Some(region) = autoclose_region {
4547 // If the selection is followed by an auto-inserted closing bracket,
4548 // then don't insert that closing bracket again; just move the selection
4549 // past the closing bracket.
4550 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4551 && text.as_ref() == region.pair.end.as_str()
4552 && snapshot.contains_str_at(region.range.end, text.as_ref());
4553 if should_skip {
4554 let anchor = snapshot.anchor_after(selection.end);
4555 new_selections
4556 .push((selection.map(|_| anchor), region.pair.end.len()));
4557 continue;
4558 }
4559 }
4560
4561 let always_treat_brackets_as_autoclosed = snapshot
4562 .language_settings_at(selection.start, cx)
4563 .always_treat_brackets_as_autoclosed;
4564 if always_treat_brackets_as_autoclosed
4565 && is_bracket_pair_end
4566 && snapshot.contains_str_at(selection.end, text.as_ref())
4567 {
4568 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4569 // and the inserted text is a closing bracket and the selection is followed
4570 // by the closing bracket then move the selection past the closing bracket.
4571 let anchor = snapshot.anchor_after(selection.end);
4572 new_selections.push((selection.map(|_| anchor), text.len()));
4573 continue;
4574 }
4575 }
4576 // If an opening bracket is 1 character long and is typed while
4577 // text is selected, then surround that text with the bracket pair.
4578 else if auto_surround
4579 && bracket_pair.surround
4580 && is_bracket_pair_start
4581 && bracket_pair.start.chars().count() == 1
4582 {
4583 edits.push((selection.start..selection.start, text.clone()));
4584 edits.push((
4585 selection.end..selection.end,
4586 bracket_pair.end.as_str().into(),
4587 ));
4588 bracket_inserted = true;
4589 new_selections.push((
4590 Selection {
4591 id: selection.id,
4592 start: snapshot.anchor_after(selection.start),
4593 end: snapshot.anchor_before(selection.end),
4594 reversed: selection.reversed,
4595 goal: selection.goal,
4596 },
4597 0,
4598 ));
4599 continue;
4600 }
4601 }
4602 }
4603
4604 if self.auto_replace_emoji_shortcode
4605 && selection.is_empty()
4606 && text.as_ref().ends_with(':')
4607 && let Some(possible_emoji_short_code) =
4608 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4609 && !possible_emoji_short_code.is_empty()
4610 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4611 {
4612 let emoji_shortcode_start = Point::new(
4613 selection.start.row,
4614 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4615 );
4616
4617 // Remove shortcode from buffer
4618 edits.push((
4619 emoji_shortcode_start..selection.start,
4620 "".to_string().into(),
4621 ));
4622 new_selections.push((
4623 Selection {
4624 id: selection.id,
4625 start: snapshot.anchor_after(emoji_shortcode_start),
4626 end: snapshot.anchor_before(selection.start),
4627 reversed: selection.reversed,
4628 goal: selection.goal,
4629 },
4630 0,
4631 ));
4632
4633 // Insert emoji
4634 let selection_start_anchor = snapshot.anchor_after(selection.start);
4635 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4636 edits.push((selection.start..selection.end, emoji.to_string().into()));
4637
4638 continue;
4639 }
4640
4641 let next_is_adjacent = regions
4642 .peek()
4643 .is_some_and(|(next, _)| selection.end == next.start);
4644
4645 // If not handling any auto-close operation, then just replace the selected
4646 // text with the given input and move the selection to the end of the
4647 // newly inserted text.
4648 let anchor = if in_adjacent_group || next_is_adjacent {
4649 // After edits the right bias would shift those anchor to the next visible fragment
4650 // but we want to resolve to the previous one
4651 snapshot.anchor_before(selection.end)
4652 } else {
4653 snapshot.anchor_after(selection.end)
4654 };
4655
4656 if !self.linked_edit_ranges.is_empty() {
4657 let start_anchor = snapshot.anchor_before(selection.start);
4658
4659 let is_word_char = text.chars().next().is_none_or(|char| {
4660 let classifier = snapshot
4661 .char_classifier_at(start_anchor.to_offset(&snapshot))
4662 .scope_context(Some(CharScopeContext::LinkedEdit));
4663 classifier.is_word(char)
4664 });
4665
4666 if is_word_char {
4667 if let Some(ranges) = self
4668 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4669 {
4670 for (buffer, edits) in ranges {
4671 linked_edits
4672 .entry(buffer.clone())
4673 .or_default()
4674 .extend(edits.into_iter().map(|range| (range, text.clone())));
4675 }
4676 }
4677 } else {
4678 clear_linked_edit_ranges = true;
4679 }
4680 }
4681
4682 new_selections.push((selection.map(|_| anchor), 0));
4683 edits.push((selection.start..selection.end, text.clone()));
4684
4685 has_adjacent_edits |= next_is_adjacent;
4686 in_adjacent_group = next_is_adjacent;
4687 }
4688
4689 if all_selections_read_only {
4690 return;
4691 }
4692
4693 drop(regions);
4694 drop(snapshot);
4695
4696 self.transact(window, cx, |this, window, cx| {
4697 if clear_linked_edit_ranges {
4698 this.linked_edit_ranges.clear();
4699 }
4700 let initial_buffer_versions =
4701 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4702
4703 this.buffer.update(cx, |buffer, cx| {
4704 if has_adjacent_edits {
4705 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4706 } else {
4707 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4708 }
4709 });
4710 for (buffer, edits) in linked_edits {
4711 buffer.update(cx, |buffer, cx| {
4712 let snapshot = buffer.snapshot();
4713 let edits = edits
4714 .into_iter()
4715 .map(|(range, text)| {
4716 use text::ToPoint as TP;
4717 let end_point = TP::to_point(&range.end, &snapshot);
4718 let start_point = TP::to_point(&range.start, &snapshot);
4719 (start_point..end_point, text)
4720 })
4721 .sorted_by_key(|(range, _)| range.start);
4722 buffer.edit(edits, None, cx);
4723 })
4724 }
4725 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4726 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4727 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4728 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4729 new_anchor_selections,
4730 &map,
4731 )
4732 .zip(new_selection_deltas)
4733 .map(|(selection, delta)| Selection {
4734 id: selection.id,
4735 start: selection.start + delta,
4736 end: selection.end + delta,
4737 reversed: selection.reversed,
4738 goal: SelectionGoal::None,
4739 })
4740 .collect::<Vec<_>>();
4741
4742 let mut i = 0;
4743 for (position, delta, selection_id, pair) in new_autoclose_regions {
4744 let position = position.to_offset(map.buffer_snapshot()) + delta;
4745 let start = map.buffer_snapshot().anchor_before(position);
4746 let end = map.buffer_snapshot().anchor_after(position);
4747 while let Some(existing_state) = this.autoclose_regions.get(i) {
4748 match existing_state
4749 .range
4750 .start
4751 .cmp(&start, map.buffer_snapshot())
4752 {
4753 Ordering::Less => i += 1,
4754 Ordering::Greater => break,
4755 Ordering::Equal => {
4756 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4757 Ordering::Less => i += 1,
4758 Ordering::Equal => break,
4759 Ordering::Greater => break,
4760 }
4761 }
4762 }
4763 }
4764 this.autoclose_regions.insert(
4765 i,
4766 AutocloseRegion {
4767 selection_id,
4768 range: start..end,
4769 pair,
4770 },
4771 );
4772 }
4773
4774 let had_active_edit_prediction = this.has_active_edit_prediction();
4775 this.change_selections(
4776 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4777 window,
4778 cx,
4779 |s| s.select(new_selections),
4780 );
4781
4782 if !bracket_inserted
4783 && let Some(on_type_format_task) =
4784 this.trigger_on_type_formatting(text.to_string(), window, cx)
4785 {
4786 on_type_format_task.detach_and_log_err(cx);
4787 }
4788
4789 let editor_settings = EditorSettings::get_global(cx);
4790 if bracket_inserted
4791 && (editor_settings.auto_signature_help
4792 || editor_settings.show_signature_help_after_edits)
4793 {
4794 this.show_signature_help(&ShowSignatureHelp, window, cx);
4795 }
4796
4797 let trigger_in_words =
4798 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4799 if this.hard_wrap.is_some() {
4800 let latest: Range<Point> = this.selections.newest(&map).range();
4801 if latest.is_empty()
4802 && this
4803 .buffer()
4804 .read(cx)
4805 .snapshot(cx)
4806 .line_len(MultiBufferRow(latest.start.row))
4807 == latest.start.column
4808 {
4809 this.rewrap_impl(
4810 RewrapOptions {
4811 override_language_settings: true,
4812 preserve_existing_whitespace: true,
4813 },
4814 cx,
4815 )
4816 }
4817 }
4818 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4819 refresh_linked_ranges(this, window, cx);
4820 this.refresh_edit_prediction(true, false, window, cx);
4821 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4822 });
4823 }
4824
4825 fn find_possible_emoji_shortcode_at_position(
4826 snapshot: &MultiBufferSnapshot,
4827 position: Point,
4828 ) -> Option<String> {
4829 let mut chars = Vec::new();
4830 let mut found_colon = false;
4831 for char in snapshot.reversed_chars_at(position).take(100) {
4832 // Found a possible emoji shortcode in the middle of the buffer
4833 if found_colon {
4834 if char.is_whitespace() {
4835 chars.reverse();
4836 return Some(chars.iter().collect());
4837 }
4838 // If the previous character is not a whitespace, we are in the middle of a word
4839 // and we only want to complete the shortcode if the word is made up of other emojis
4840 let mut containing_word = String::new();
4841 for ch in snapshot
4842 .reversed_chars_at(position)
4843 .skip(chars.len() + 1)
4844 .take(100)
4845 {
4846 if ch.is_whitespace() {
4847 break;
4848 }
4849 containing_word.push(ch);
4850 }
4851 let containing_word = containing_word.chars().rev().collect::<String>();
4852 if util::word_consists_of_emojis(containing_word.as_str()) {
4853 chars.reverse();
4854 return Some(chars.iter().collect());
4855 }
4856 }
4857
4858 if char.is_whitespace() || !char.is_ascii() {
4859 return None;
4860 }
4861 if char == ':' {
4862 found_colon = true;
4863 } else {
4864 chars.push(char);
4865 }
4866 }
4867 // Found a possible emoji shortcode at the beginning of the buffer
4868 chars.reverse();
4869 Some(chars.iter().collect())
4870 }
4871
4872 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4874 self.transact(window, cx, |this, window, cx| {
4875 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4876 let selections = this
4877 .selections
4878 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4879 let multi_buffer = this.buffer.read(cx);
4880 let buffer = multi_buffer.snapshot(cx);
4881 selections
4882 .iter()
4883 .map(|selection| {
4884 let start_point = selection.start.to_point(&buffer);
4885 let mut existing_indent =
4886 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4887 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4888 let start = selection.start;
4889 let end = selection.end;
4890 let selection_is_empty = start == end;
4891 let language_scope = buffer.language_scope_at(start);
4892 let (delimiter, newline_config) = if let Some(language) = &language_scope {
4893 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
4894 &buffer,
4895 start..end,
4896 language,
4897 )
4898 || NewlineConfig::insert_extra_newline_tree_sitter(
4899 &buffer,
4900 start..end,
4901 );
4902
4903 let mut newline_config = NewlineConfig::Newline {
4904 additional_indent: IndentSize::spaces(0),
4905 extra_line_additional_indent: if needs_extra_newline {
4906 Some(IndentSize::spaces(0))
4907 } else {
4908 None
4909 },
4910 prevent_auto_indent: false,
4911 };
4912
4913 let comment_delimiter = maybe!({
4914 if !selection_is_empty {
4915 return None;
4916 }
4917
4918 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4919 return None;
4920 }
4921
4922 return comment_delimiter_for_newline(
4923 &start_point,
4924 &buffer,
4925 language,
4926 );
4927 });
4928
4929 let doc_delimiter = maybe!({
4930 if !selection_is_empty {
4931 return None;
4932 }
4933
4934 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4935 return None;
4936 }
4937
4938 return documentation_delimiter_for_newline(
4939 &start_point,
4940 &buffer,
4941 language,
4942 &mut newline_config,
4943 );
4944 });
4945
4946 let list_delimiter = maybe!({
4947 if !selection_is_empty {
4948 return None;
4949 }
4950
4951 if !multi_buffer.language_settings(cx).extend_list_on_newline {
4952 return None;
4953 }
4954
4955 return list_delimiter_for_newline(
4956 &start_point,
4957 &buffer,
4958 language,
4959 &mut newline_config,
4960 );
4961 });
4962
4963 (
4964 comment_delimiter.or(doc_delimiter).or(list_delimiter),
4965 newline_config,
4966 )
4967 } else {
4968 (
4969 None,
4970 NewlineConfig::Newline {
4971 additional_indent: IndentSize::spaces(0),
4972 extra_line_additional_indent: None,
4973 prevent_auto_indent: false,
4974 },
4975 )
4976 };
4977
4978 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
4979 NewlineConfig::ClearCurrentLine => {
4980 let row_start =
4981 buffer.point_to_offset(Point::new(start_point.row, 0));
4982 (row_start, String::new(), false)
4983 }
4984 NewlineConfig::UnindentCurrentLine { continuation } => {
4985 let row_start =
4986 buffer.point_to_offset(Point::new(start_point.row, 0));
4987 let tab_size = buffer.language_settings_at(start, cx).tab_size;
4988 let tab_size_indent = IndentSize::spaces(tab_size.get());
4989 let reduced_indent =
4990 existing_indent.with_delta(Ordering::Less, tab_size_indent);
4991 let mut new_text = String::new();
4992 new_text.extend(reduced_indent.chars());
4993 new_text.push_str(continuation);
4994 (row_start, new_text, true)
4995 }
4996 NewlineConfig::Newline {
4997 additional_indent,
4998 extra_line_additional_indent,
4999 prevent_auto_indent,
5000 } => {
5001 let capacity_for_delimiter =
5002 delimiter.as_deref().map(str::len).unwrap_or_default();
5003 let extra_line_len = extra_line_additional_indent
5004 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5005 .unwrap_or(0);
5006 let mut new_text = String::with_capacity(
5007 1 + capacity_for_delimiter
5008 + existing_indent.len as usize
5009 + additional_indent.len as usize
5010 + extra_line_len,
5011 );
5012 new_text.push('\n');
5013 new_text.extend(existing_indent.chars());
5014 new_text.extend(additional_indent.chars());
5015 if let Some(delimiter) = &delimiter {
5016 new_text.push_str(delimiter);
5017 }
5018 if let Some(extra_indent) = extra_line_additional_indent {
5019 new_text.push('\n');
5020 new_text.extend(existing_indent.chars());
5021 new_text.extend(extra_indent.chars());
5022 }
5023 (start, new_text, *prevent_auto_indent)
5024 }
5025 };
5026
5027 let anchor = buffer.anchor_after(end);
5028 let new_selection = selection.map(|_| anchor);
5029 (
5030 ((edit_start..end, new_text), prevent_auto_indent),
5031 (newline_config.has_extra_line(), new_selection),
5032 )
5033 })
5034 .unzip()
5035 };
5036
5037 let mut auto_indent_edits = Vec::new();
5038 let mut edits = Vec::new();
5039 for (edit, prevent_auto_indent) in edits_with_flags {
5040 if prevent_auto_indent {
5041 edits.push(edit);
5042 } else {
5043 auto_indent_edits.push(edit);
5044 }
5045 }
5046 if !edits.is_empty() {
5047 this.edit(edits, cx);
5048 }
5049 if !auto_indent_edits.is_empty() {
5050 this.edit_with_autoindent(auto_indent_edits, cx);
5051 }
5052
5053 let buffer = this.buffer.read(cx).snapshot(cx);
5054 let new_selections = selection_info
5055 .into_iter()
5056 .map(|(extra_newline_inserted, new_selection)| {
5057 let mut cursor = new_selection.end.to_point(&buffer);
5058 if extra_newline_inserted {
5059 cursor.row -= 1;
5060 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5061 }
5062 new_selection.map(|_| cursor)
5063 })
5064 .collect();
5065
5066 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5067 this.refresh_edit_prediction(true, false, window, cx);
5068 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5069 task.detach_and_log_err(cx);
5070 }
5071 });
5072 }
5073
5074 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5075 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5076
5077 let buffer = self.buffer.read(cx);
5078 let snapshot = buffer.snapshot(cx);
5079
5080 let mut edits = Vec::new();
5081 let mut rows = Vec::new();
5082
5083 for (rows_inserted, selection) in self
5084 .selections
5085 .all_adjusted(&self.display_snapshot(cx))
5086 .into_iter()
5087 .enumerate()
5088 {
5089 let cursor = selection.head();
5090 let row = cursor.row;
5091
5092 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5093
5094 let newline = "\n".to_string();
5095 edits.push((start_of_line..start_of_line, newline));
5096
5097 rows.push(row + rows_inserted as u32);
5098 }
5099
5100 self.transact(window, cx, |editor, window, cx| {
5101 editor.edit(edits, cx);
5102
5103 editor.change_selections(Default::default(), window, cx, |s| {
5104 let mut index = 0;
5105 s.move_cursors_with(|map, _, _| {
5106 let row = rows[index];
5107 index += 1;
5108
5109 let point = Point::new(row, 0);
5110 let boundary = map.next_line_boundary(point).1;
5111 let clipped = map.clip_point(boundary, Bias::Left);
5112
5113 (clipped, SelectionGoal::None)
5114 });
5115 });
5116
5117 let mut indent_edits = Vec::new();
5118 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5119 for row in rows {
5120 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5121 for (row, indent) in indents {
5122 if indent.len == 0 {
5123 continue;
5124 }
5125
5126 let text = match indent.kind {
5127 IndentKind::Space => " ".repeat(indent.len as usize),
5128 IndentKind::Tab => "\t".repeat(indent.len as usize),
5129 };
5130 let point = Point::new(row.0, 0);
5131 indent_edits.push((point..point, text));
5132 }
5133 }
5134 editor.edit(indent_edits, cx);
5135 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5136 format.detach_and_log_err(cx);
5137 }
5138 });
5139 }
5140
5141 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5143
5144 let buffer = self.buffer.read(cx);
5145 let snapshot = buffer.snapshot(cx);
5146
5147 let mut edits = Vec::new();
5148 let mut rows = Vec::new();
5149 let mut rows_inserted = 0;
5150
5151 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5152 let cursor = selection.head();
5153 let row = cursor.row;
5154
5155 let point = Point::new(row + 1, 0);
5156 let start_of_line = snapshot.clip_point(point, Bias::Left);
5157
5158 let newline = "\n".to_string();
5159 edits.push((start_of_line..start_of_line, newline));
5160
5161 rows_inserted += 1;
5162 rows.push(row + rows_inserted);
5163 }
5164
5165 self.transact(window, cx, |editor, window, cx| {
5166 editor.edit(edits, cx);
5167
5168 editor.change_selections(Default::default(), window, cx, |s| {
5169 let mut index = 0;
5170 s.move_cursors_with(|map, _, _| {
5171 let row = rows[index];
5172 index += 1;
5173
5174 let point = Point::new(row, 0);
5175 let boundary = map.next_line_boundary(point).1;
5176 let clipped = map.clip_point(boundary, Bias::Left);
5177
5178 (clipped, SelectionGoal::None)
5179 });
5180 });
5181
5182 let mut indent_edits = Vec::new();
5183 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5184 for row in rows {
5185 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5186 for (row, indent) in indents {
5187 if indent.len == 0 {
5188 continue;
5189 }
5190
5191 let text = match indent.kind {
5192 IndentKind::Space => " ".repeat(indent.len as usize),
5193 IndentKind::Tab => "\t".repeat(indent.len as usize),
5194 };
5195 let point = Point::new(row.0, 0);
5196 indent_edits.push((point..point, text));
5197 }
5198 }
5199 editor.edit(indent_edits, cx);
5200 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5201 format.detach_and_log_err(cx);
5202 }
5203 });
5204 }
5205
5206 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5207 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5208 original_indent_columns: Vec::new(),
5209 });
5210 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5211 }
5212
5213 fn insert_with_autoindent_mode(
5214 &mut self,
5215 text: &str,
5216 autoindent_mode: Option<AutoindentMode>,
5217 window: &mut Window,
5218 cx: &mut Context<Self>,
5219 ) {
5220 if self.read_only(cx) {
5221 return;
5222 }
5223
5224 let text: Arc<str> = text.into();
5225 self.transact(window, cx, |this, window, cx| {
5226 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5227 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5228 let anchors = {
5229 let snapshot = buffer.read(cx);
5230 old_selections
5231 .iter()
5232 .map(|s| {
5233 let anchor = snapshot.anchor_after(s.head());
5234 s.map(|_| anchor)
5235 })
5236 .collect::<Vec<_>>()
5237 };
5238 buffer.edit(
5239 old_selections
5240 .iter()
5241 .map(|s| (s.start..s.end, text.clone())),
5242 autoindent_mode,
5243 cx,
5244 );
5245 anchors
5246 });
5247
5248 this.change_selections(Default::default(), window, cx, |s| {
5249 s.select_anchors(selection_anchors);
5250 });
5251
5252 cx.notify();
5253 });
5254 }
5255
5256 fn trigger_completion_on_input(
5257 &mut self,
5258 text: &str,
5259 trigger_in_words: bool,
5260 window: &mut Window,
5261 cx: &mut Context<Self>,
5262 ) {
5263 let completions_source = self
5264 .context_menu
5265 .borrow()
5266 .as_ref()
5267 .and_then(|menu| match menu {
5268 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5269 CodeContextMenu::CodeActions(_) => None,
5270 });
5271
5272 match completions_source {
5273 Some(CompletionsMenuSource::Words { .. }) => {
5274 self.open_or_update_completions_menu(
5275 Some(CompletionsMenuSource::Words {
5276 ignore_threshold: false,
5277 }),
5278 None,
5279 trigger_in_words,
5280 window,
5281 cx,
5282 );
5283 }
5284 _ => self.open_or_update_completions_menu(
5285 None,
5286 Some(text.to_owned()).filter(|x| !x.is_empty()),
5287 true,
5288 window,
5289 cx,
5290 ),
5291 }
5292 }
5293
5294 /// If any empty selections is touching the start of its innermost containing autoclose
5295 /// region, expand it to select the brackets.
5296 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5297 let selections = self
5298 .selections
5299 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5300 let buffer = self.buffer.read(cx).read(cx);
5301 let new_selections = self
5302 .selections_with_autoclose_regions(selections, &buffer)
5303 .map(|(mut selection, region)| {
5304 if !selection.is_empty() {
5305 return selection;
5306 }
5307
5308 if let Some(region) = region {
5309 let mut range = region.range.to_offset(&buffer);
5310 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5311 range.start -= region.pair.start.len();
5312 if buffer.contains_str_at(range.start, ®ion.pair.start)
5313 && buffer.contains_str_at(range.end, ®ion.pair.end)
5314 {
5315 range.end += region.pair.end.len();
5316 selection.start = range.start;
5317 selection.end = range.end;
5318
5319 return selection;
5320 }
5321 }
5322 }
5323
5324 let always_treat_brackets_as_autoclosed = buffer
5325 .language_settings_at(selection.start, cx)
5326 .always_treat_brackets_as_autoclosed;
5327
5328 if !always_treat_brackets_as_autoclosed {
5329 return selection;
5330 }
5331
5332 if let Some(scope) = buffer.language_scope_at(selection.start) {
5333 for (pair, enabled) in scope.brackets() {
5334 if !enabled || !pair.close {
5335 continue;
5336 }
5337
5338 if buffer.contains_str_at(selection.start, &pair.end) {
5339 let pair_start_len = pair.start.len();
5340 if buffer.contains_str_at(
5341 selection.start.saturating_sub_usize(pair_start_len),
5342 &pair.start,
5343 ) {
5344 selection.start -= pair_start_len;
5345 selection.end += pair.end.len();
5346
5347 return selection;
5348 }
5349 }
5350 }
5351 }
5352
5353 selection
5354 })
5355 .collect();
5356
5357 drop(buffer);
5358 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5359 selections.select(new_selections)
5360 });
5361 }
5362
5363 /// Iterate the given selections, and for each one, find the smallest surrounding
5364 /// autoclose region. This uses the ordering of the selections and the autoclose
5365 /// regions to avoid repeated comparisons.
5366 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5367 &'a self,
5368 selections: impl IntoIterator<Item = Selection<D>>,
5369 buffer: &'a MultiBufferSnapshot,
5370 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5371 let mut i = 0;
5372 let mut regions = self.autoclose_regions.as_slice();
5373 selections.into_iter().map(move |selection| {
5374 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5375
5376 let mut enclosing = None;
5377 while let Some(pair_state) = regions.get(i) {
5378 if pair_state.range.end.to_offset(buffer) < range.start {
5379 regions = ®ions[i + 1..];
5380 i = 0;
5381 } else if pair_state.range.start.to_offset(buffer) > range.end {
5382 break;
5383 } else {
5384 if pair_state.selection_id == selection.id {
5385 enclosing = Some(pair_state);
5386 }
5387 i += 1;
5388 }
5389 }
5390
5391 (selection, enclosing)
5392 })
5393 }
5394
5395 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5396 fn invalidate_autoclose_regions(
5397 &mut self,
5398 mut selections: &[Selection<Anchor>],
5399 buffer: &MultiBufferSnapshot,
5400 ) {
5401 self.autoclose_regions.retain(|state| {
5402 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5403 return false;
5404 }
5405
5406 let mut i = 0;
5407 while let Some(selection) = selections.get(i) {
5408 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5409 selections = &selections[1..];
5410 continue;
5411 }
5412 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5413 break;
5414 }
5415 if selection.id == state.selection_id {
5416 return true;
5417 } else {
5418 i += 1;
5419 }
5420 }
5421 false
5422 });
5423 }
5424
5425 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5426 let offset = position.to_offset(buffer);
5427 let (word_range, kind) =
5428 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5429 if offset > word_range.start && kind == Some(CharKind::Word) {
5430 Some(
5431 buffer
5432 .text_for_range(word_range.start..offset)
5433 .collect::<String>(),
5434 )
5435 } else {
5436 None
5437 }
5438 }
5439
5440 pub fn visible_excerpts(
5441 &self,
5442 lsp_related_only: bool,
5443 cx: &mut Context<Editor>,
5444 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5445 let project = self.project().cloned();
5446 let multi_buffer = self.buffer().read(cx);
5447 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5448 let multi_buffer_visible_start = self
5449 .scroll_manager
5450 .anchor()
5451 .anchor
5452 .to_point(&multi_buffer_snapshot);
5453 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5454 multi_buffer_visible_start
5455 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5456 Bias::Left,
5457 );
5458 multi_buffer_snapshot
5459 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5460 .into_iter()
5461 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5462 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5463 if !lsp_related_only {
5464 return Some((
5465 excerpt_id,
5466 (
5467 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5468 buffer.version().clone(),
5469 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5470 ),
5471 ));
5472 }
5473
5474 let project = project.as_ref()?.read(cx);
5475 let buffer_file = project::File::from_dyn(buffer.file())?;
5476 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5477 let worktree_entry = buffer_worktree
5478 .read(cx)
5479 .entry_for_id(buffer_file.project_entry_id()?)?;
5480 if worktree_entry.is_ignored {
5481 None
5482 } else {
5483 Some((
5484 excerpt_id,
5485 (
5486 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5487 buffer.version().clone(),
5488 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5489 ),
5490 ))
5491 }
5492 })
5493 .collect()
5494 }
5495
5496 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5497 TextLayoutDetails {
5498 text_system: window.text_system().clone(),
5499 editor_style: self.style.clone().unwrap(),
5500 rem_size: window.rem_size(),
5501 scroll_anchor: self.scroll_manager.anchor(),
5502 visible_rows: self.visible_line_count(),
5503 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5504 }
5505 }
5506
5507 fn trigger_on_type_formatting(
5508 &self,
5509 input: String,
5510 window: &mut Window,
5511 cx: &mut Context<Self>,
5512 ) -> Option<Task<Result<()>>> {
5513 if input.chars().count() != 1 {
5514 return None;
5515 }
5516
5517 let project = self.project()?;
5518 let position = self.selections.newest_anchor().head();
5519 let (buffer, buffer_position) = self
5520 .buffer
5521 .read(cx)
5522 .text_anchor_for_position(position, cx)?;
5523
5524 let settings = language_settings::language_settings(
5525 buffer
5526 .read(cx)
5527 .language_at(buffer_position)
5528 .map(|l| l.name()),
5529 buffer.read(cx).file(),
5530 cx,
5531 );
5532 if !settings.use_on_type_format {
5533 return None;
5534 }
5535
5536 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5537 // hence we do LSP request & edit on host side only — add formats to host's history.
5538 let push_to_lsp_host_history = true;
5539 // If this is not the host, append its history with new edits.
5540 let push_to_client_history = project.read(cx).is_via_collab();
5541
5542 let on_type_formatting = project.update(cx, |project, cx| {
5543 project.on_type_format(
5544 buffer.clone(),
5545 buffer_position,
5546 input,
5547 push_to_lsp_host_history,
5548 cx,
5549 )
5550 });
5551 Some(cx.spawn_in(window, async move |editor, cx| {
5552 if let Some(transaction) = on_type_formatting.await? {
5553 if push_to_client_history {
5554 buffer.update(cx, |buffer, _| {
5555 buffer.push_transaction(transaction, Instant::now());
5556 buffer.finalize_last_transaction();
5557 });
5558 }
5559 editor.update(cx, |editor, cx| {
5560 editor.refresh_document_highlights(cx);
5561 })?;
5562 }
5563 Ok(())
5564 }))
5565 }
5566
5567 pub fn show_word_completions(
5568 &mut self,
5569 _: &ShowWordCompletions,
5570 window: &mut Window,
5571 cx: &mut Context<Self>,
5572 ) {
5573 self.open_or_update_completions_menu(
5574 Some(CompletionsMenuSource::Words {
5575 ignore_threshold: true,
5576 }),
5577 None,
5578 false,
5579 window,
5580 cx,
5581 );
5582 }
5583
5584 pub fn show_completions(
5585 &mut self,
5586 _: &ShowCompletions,
5587 window: &mut Window,
5588 cx: &mut Context<Self>,
5589 ) {
5590 self.open_or_update_completions_menu(None, None, false, window, cx);
5591 }
5592
5593 fn open_or_update_completions_menu(
5594 &mut self,
5595 requested_source: Option<CompletionsMenuSource>,
5596 trigger: Option<String>,
5597 trigger_in_words: bool,
5598 window: &mut Window,
5599 cx: &mut Context<Self>,
5600 ) {
5601 if self.pending_rename.is_some() {
5602 return;
5603 }
5604
5605 let completions_source = self
5606 .context_menu
5607 .borrow()
5608 .as_ref()
5609 .and_then(|menu| match menu {
5610 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5611 CodeContextMenu::CodeActions(_) => None,
5612 });
5613
5614 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5615
5616 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5617 // inserted and selected. To handle that case, the start of the selection is used so that
5618 // the menu starts with all choices.
5619 let position = self
5620 .selections
5621 .newest_anchor()
5622 .start
5623 .bias_right(&multibuffer_snapshot);
5624 if position.diff_base_anchor.is_some() {
5625 return;
5626 }
5627 let buffer_position = multibuffer_snapshot.anchor_before(position);
5628 let Some(buffer) = buffer_position
5629 .text_anchor
5630 .buffer_id
5631 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5632 else {
5633 return;
5634 };
5635 let buffer_snapshot = buffer.read(cx).snapshot();
5636
5637 let menu_is_open = matches!(
5638 self.context_menu.borrow().as_ref(),
5639 Some(CodeContextMenu::Completions(_))
5640 );
5641
5642 let language = buffer_snapshot
5643 .language_at(buffer_position.text_anchor)
5644 .map(|language| language.name());
5645
5646 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5647 let completion_settings = language_settings.completions.clone();
5648
5649 let show_completions_on_input = self
5650 .show_completions_on_input_override
5651 .unwrap_or(language_settings.show_completions_on_input);
5652 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5653 return;
5654 }
5655
5656 let query: Option<Arc<String>> =
5657 Self::completion_query(&multibuffer_snapshot, buffer_position)
5658 .map(|query| query.into());
5659
5660 drop(multibuffer_snapshot);
5661
5662 // Hide the current completions menu when query is empty. Without this, cached
5663 // completions from before the trigger char may be reused (#32774).
5664 if query.is_none() && menu_is_open {
5665 self.hide_context_menu(window, cx);
5666 }
5667
5668 let mut ignore_word_threshold = false;
5669 let provider = match requested_source {
5670 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5671 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5672 ignore_word_threshold = ignore_threshold;
5673 None
5674 }
5675 Some(CompletionsMenuSource::SnippetChoices)
5676 | Some(CompletionsMenuSource::SnippetsOnly) => {
5677 log::error!("bug: SnippetChoices requested_source is not handled");
5678 None
5679 }
5680 };
5681
5682 let sort_completions = provider
5683 .as_ref()
5684 .is_some_and(|provider| provider.sort_completions());
5685
5686 let filter_completions = provider
5687 .as_ref()
5688 .is_none_or(|provider| provider.filter_completions());
5689
5690 let was_snippets_only = matches!(
5691 completions_source,
5692 Some(CompletionsMenuSource::SnippetsOnly)
5693 );
5694
5695 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5696 if filter_completions {
5697 menu.filter(
5698 query.clone().unwrap_or_default(),
5699 buffer_position.text_anchor,
5700 &buffer,
5701 provider.clone(),
5702 window,
5703 cx,
5704 );
5705 }
5706 // When `is_incomplete` is false, no need to re-query completions when the current query
5707 // is a suffix of the initial query.
5708 let was_complete = !menu.is_incomplete;
5709 if was_complete && !was_snippets_only {
5710 // If the new query is a suffix of the old query (typing more characters) and
5711 // the previous result was complete, the existing completions can be filtered.
5712 //
5713 // Note that snippet completions are always complete.
5714 let query_matches = match (&menu.initial_query, &query) {
5715 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5716 (None, _) => true,
5717 _ => false,
5718 };
5719 if query_matches {
5720 let position_matches = if menu.initial_position == position {
5721 true
5722 } else {
5723 let snapshot = self.buffer.read(cx).read(cx);
5724 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5725 };
5726 if position_matches {
5727 return;
5728 }
5729 }
5730 }
5731 };
5732
5733 let Anchor {
5734 excerpt_id: buffer_excerpt_id,
5735 text_anchor: buffer_position,
5736 ..
5737 } = buffer_position;
5738
5739 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5740 buffer_snapshot.surrounding_word(buffer_position, None)
5741 {
5742 let word_to_exclude = buffer_snapshot
5743 .text_for_range(word_range.clone())
5744 .collect::<String>();
5745 (
5746 buffer_snapshot.anchor_before(word_range.start)
5747 ..buffer_snapshot.anchor_after(buffer_position),
5748 Some(word_to_exclude),
5749 )
5750 } else {
5751 (buffer_position..buffer_position, None)
5752 };
5753
5754 let show_completion_documentation = buffer_snapshot
5755 .settings_at(buffer_position, cx)
5756 .show_completion_documentation;
5757
5758 // The document can be large, so stay in reasonable bounds when searching for words,
5759 // otherwise completion pop-up might be slow to appear.
5760 const WORD_LOOKUP_ROWS: u32 = 5_000;
5761 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5762 let min_word_search = buffer_snapshot.clip_point(
5763 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5764 Bias::Left,
5765 );
5766 let max_word_search = buffer_snapshot.clip_point(
5767 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5768 Bias::Right,
5769 );
5770 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5771 ..buffer_snapshot.point_to_offset(max_word_search);
5772
5773 let skip_digits = query
5774 .as_ref()
5775 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5776
5777 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5778 trigger.as_ref().is_none_or(|trigger| {
5779 provider.is_completion_trigger(
5780 &buffer,
5781 position.text_anchor,
5782 trigger,
5783 trigger_in_words,
5784 cx,
5785 )
5786 })
5787 });
5788
5789 let provider_responses = if let Some(provider) = &provider
5790 && load_provider_completions
5791 {
5792 let trigger_character =
5793 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5794 let completion_context = CompletionContext {
5795 trigger_kind: match &trigger_character {
5796 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5797 None => CompletionTriggerKind::INVOKED,
5798 },
5799 trigger_character,
5800 };
5801
5802 provider.completions(
5803 buffer_excerpt_id,
5804 &buffer,
5805 buffer_position,
5806 completion_context,
5807 window,
5808 cx,
5809 )
5810 } else {
5811 Task::ready(Ok(Vec::new()))
5812 };
5813
5814 let load_word_completions = if !self.word_completions_enabled {
5815 false
5816 } else if requested_source
5817 == Some(CompletionsMenuSource::Words {
5818 ignore_threshold: true,
5819 })
5820 {
5821 true
5822 } else {
5823 load_provider_completions
5824 && completion_settings.words != WordsCompletionMode::Disabled
5825 && (ignore_word_threshold || {
5826 let words_min_length = completion_settings.words_min_length;
5827 // check whether word has at least `words_min_length` characters
5828 let query_chars = query.iter().flat_map(|q| q.chars());
5829 query_chars.take(words_min_length).count() == words_min_length
5830 })
5831 };
5832
5833 let mut words = if load_word_completions {
5834 cx.background_spawn({
5835 let buffer_snapshot = buffer_snapshot.clone();
5836 async move {
5837 buffer_snapshot.words_in_range(WordsQuery {
5838 fuzzy_contents: None,
5839 range: word_search_range,
5840 skip_digits,
5841 })
5842 }
5843 })
5844 } else {
5845 Task::ready(BTreeMap::default())
5846 };
5847
5848 let snippets = if let Some(provider) = &provider
5849 && provider.show_snippets()
5850 && let Some(project) = self.project()
5851 {
5852 let char_classifier = buffer_snapshot
5853 .char_classifier_at(buffer_position)
5854 .scope_context(Some(CharScopeContext::Completion));
5855 project.update(cx, |project, cx| {
5856 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5857 })
5858 } else {
5859 Task::ready(Ok(CompletionResponse {
5860 completions: Vec::new(),
5861 display_options: Default::default(),
5862 is_incomplete: false,
5863 }))
5864 };
5865
5866 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5867
5868 let id = post_inc(&mut self.next_completion_id);
5869 let task = cx.spawn_in(window, async move |editor, cx| {
5870 let Ok(()) = editor.update(cx, |this, _| {
5871 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5872 }) else {
5873 return;
5874 };
5875
5876 // TODO: Ideally completions from different sources would be selectively re-queried, so
5877 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5878 let mut completions = Vec::new();
5879 let mut is_incomplete = false;
5880 let mut display_options: Option<CompletionDisplayOptions> = None;
5881 if let Some(provider_responses) = provider_responses.await.log_err()
5882 && !provider_responses.is_empty()
5883 {
5884 for response in provider_responses {
5885 completions.extend(response.completions);
5886 is_incomplete = is_incomplete || response.is_incomplete;
5887 match display_options.as_mut() {
5888 None => {
5889 display_options = Some(response.display_options);
5890 }
5891 Some(options) => options.merge(&response.display_options),
5892 }
5893 }
5894 if completion_settings.words == WordsCompletionMode::Fallback {
5895 words = Task::ready(BTreeMap::default());
5896 }
5897 }
5898 let display_options = display_options.unwrap_or_default();
5899
5900 let mut words = words.await;
5901 if let Some(word_to_exclude) = &word_to_exclude {
5902 words.remove(word_to_exclude);
5903 }
5904 for lsp_completion in &completions {
5905 words.remove(&lsp_completion.new_text);
5906 }
5907 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5908 replace_range: word_replace_range.clone(),
5909 new_text: word.clone(),
5910 label: CodeLabel::plain(word, None),
5911 match_start: None,
5912 snippet_deduplication_key: None,
5913 icon_path: None,
5914 documentation: None,
5915 source: CompletionSource::BufferWord {
5916 word_range,
5917 resolved: false,
5918 },
5919 insert_text_mode: Some(InsertTextMode::AS_IS),
5920 confirm: None,
5921 }));
5922
5923 completions.extend(
5924 snippets
5925 .await
5926 .into_iter()
5927 .flat_map(|response| response.completions),
5928 );
5929
5930 let menu = if completions.is_empty() {
5931 None
5932 } else {
5933 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5934 let languages = editor
5935 .workspace
5936 .as_ref()
5937 .and_then(|(workspace, _)| workspace.upgrade())
5938 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5939 let menu = CompletionsMenu::new(
5940 id,
5941 requested_source.unwrap_or(if load_provider_completions {
5942 CompletionsMenuSource::Normal
5943 } else {
5944 CompletionsMenuSource::SnippetsOnly
5945 }),
5946 sort_completions,
5947 show_completion_documentation,
5948 position,
5949 query.clone(),
5950 is_incomplete,
5951 buffer.clone(),
5952 completions.into(),
5953 editor
5954 .context_menu()
5955 .borrow_mut()
5956 .as_ref()
5957 .map(|menu| menu.primary_scroll_handle()),
5958 display_options,
5959 snippet_sort_order,
5960 languages,
5961 language,
5962 cx,
5963 );
5964
5965 let query = if filter_completions { query } else { None };
5966 let matches_task = menu.do_async_filtering(
5967 query.unwrap_or_default(),
5968 buffer_position,
5969 &buffer,
5970 cx,
5971 );
5972 (menu, matches_task)
5973 }) else {
5974 return;
5975 };
5976
5977 let matches = matches_task.await;
5978
5979 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5980 // Newer menu already set, so exit.
5981 if let Some(CodeContextMenu::Completions(prev_menu)) =
5982 editor.context_menu.borrow().as_ref()
5983 && prev_menu.id > id
5984 {
5985 return;
5986 };
5987
5988 // Only valid to take prev_menu because either the new menu is immediately set
5989 // below, or the menu is hidden.
5990 if let Some(CodeContextMenu::Completions(prev_menu)) =
5991 editor.context_menu.borrow_mut().take()
5992 {
5993 let position_matches =
5994 if prev_menu.initial_position == menu.initial_position {
5995 true
5996 } else {
5997 let snapshot = editor.buffer.read(cx).read(cx);
5998 prev_menu.initial_position.to_offset(&snapshot)
5999 == menu.initial_position.to_offset(&snapshot)
6000 };
6001 if position_matches {
6002 // Preserve markdown cache before `set_filter_results` because it will
6003 // try to populate the documentation cache.
6004 menu.preserve_markdown_cache(prev_menu);
6005 }
6006 };
6007
6008 menu.set_filter_results(matches, provider, window, cx);
6009 }) else {
6010 return;
6011 };
6012
6013 menu.visible().then_some(menu)
6014 };
6015
6016 editor
6017 .update_in(cx, |editor, window, cx| {
6018 if editor.focus_handle.is_focused(window)
6019 && let Some(menu) = menu
6020 {
6021 *editor.context_menu.borrow_mut() =
6022 Some(CodeContextMenu::Completions(menu));
6023
6024 crate::hover_popover::hide_hover(editor, cx);
6025 if editor.show_edit_predictions_in_menu() {
6026 editor.update_visible_edit_prediction(window, cx);
6027 } else {
6028 editor.discard_edit_prediction(false, cx);
6029 }
6030
6031 cx.notify();
6032 return;
6033 }
6034
6035 if editor.completion_tasks.len() <= 1 {
6036 // If there are no more completion tasks and the last menu was empty, we should hide it.
6037 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6038 // If it was already hidden and we don't show edit predictions in the menu,
6039 // we should also show the edit prediction when available.
6040 if was_hidden && editor.show_edit_predictions_in_menu() {
6041 editor.update_visible_edit_prediction(window, cx);
6042 }
6043 }
6044 })
6045 .ok();
6046 });
6047
6048 self.completion_tasks.push((id, task));
6049 }
6050
6051 #[cfg(feature = "test-support")]
6052 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6053 let menu = self.context_menu.borrow();
6054 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6055 let completions = menu.completions.borrow();
6056 Some(completions.to_vec())
6057 } else {
6058 None
6059 }
6060 }
6061
6062 pub fn with_completions_menu_matching_id<R>(
6063 &self,
6064 id: CompletionId,
6065 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6066 ) -> R {
6067 let mut context_menu = self.context_menu.borrow_mut();
6068 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6069 return f(None);
6070 };
6071 if completions_menu.id != id {
6072 return f(None);
6073 }
6074 f(Some(completions_menu))
6075 }
6076
6077 pub fn confirm_completion(
6078 &mut self,
6079 action: &ConfirmCompletion,
6080 window: &mut Window,
6081 cx: &mut Context<Self>,
6082 ) -> Option<Task<Result<()>>> {
6083 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6084 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6085 }
6086
6087 pub fn confirm_completion_insert(
6088 &mut self,
6089 _: &ConfirmCompletionInsert,
6090 window: &mut Window,
6091 cx: &mut Context<Self>,
6092 ) -> Option<Task<Result<()>>> {
6093 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6094 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6095 }
6096
6097 pub fn confirm_completion_replace(
6098 &mut self,
6099 _: &ConfirmCompletionReplace,
6100 window: &mut Window,
6101 cx: &mut Context<Self>,
6102 ) -> Option<Task<Result<()>>> {
6103 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6104 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6105 }
6106
6107 pub fn compose_completion(
6108 &mut self,
6109 action: &ComposeCompletion,
6110 window: &mut Window,
6111 cx: &mut Context<Self>,
6112 ) -> Option<Task<Result<()>>> {
6113 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6114 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6115 }
6116
6117 fn do_completion(
6118 &mut self,
6119 item_ix: Option<usize>,
6120 intent: CompletionIntent,
6121 window: &mut Window,
6122 cx: &mut Context<Editor>,
6123 ) -> Option<Task<Result<()>>> {
6124 use language::ToOffset as _;
6125
6126 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6127 else {
6128 return None;
6129 };
6130
6131 let candidate_id = {
6132 let entries = completions_menu.entries.borrow();
6133 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6134 if self.show_edit_predictions_in_menu() {
6135 self.discard_edit_prediction(true, cx);
6136 }
6137 mat.candidate_id
6138 };
6139
6140 let completion = completions_menu
6141 .completions
6142 .borrow()
6143 .get(candidate_id)?
6144 .clone();
6145 cx.stop_propagation();
6146
6147 let buffer_handle = completions_menu.buffer.clone();
6148
6149 let CompletionEdit {
6150 new_text,
6151 snippet,
6152 replace_range,
6153 } = process_completion_for_edit(
6154 &completion,
6155 intent,
6156 &buffer_handle,
6157 &completions_menu.initial_position.text_anchor,
6158 cx,
6159 );
6160
6161 let buffer = buffer_handle.read(cx);
6162 let snapshot = self.buffer.read(cx).snapshot(cx);
6163 let newest_anchor = self.selections.newest_anchor();
6164 let replace_range_multibuffer = {
6165 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6166 excerpt.map_range_from_buffer(replace_range.clone())
6167 };
6168 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6169 return None;
6170 }
6171
6172 let old_text = buffer
6173 .text_for_range(replace_range.clone())
6174 .collect::<String>();
6175 let lookbehind = newest_anchor
6176 .start
6177 .text_anchor
6178 .to_offset(buffer)
6179 .saturating_sub(replace_range.start.0);
6180 let lookahead = replace_range
6181 .end
6182 .0
6183 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6184 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6185 let suffix = &old_text[lookbehind.min(old_text.len())..];
6186
6187 let selections = self
6188 .selections
6189 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6190 let mut ranges = Vec::new();
6191 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6192
6193 for selection in &selections {
6194 let range = if selection.id == newest_anchor.id {
6195 replace_range_multibuffer.clone()
6196 } else {
6197 let mut range = selection.range();
6198
6199 // if prefix is present, don't duplicate it
6200 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6201 range.start = range.start.saturating_sub_usize(lookbehind);
6202
6203 // if suffix is also present, mimic the newest cursor and replace it
6204 if selection.id != newest_anchor.id
6205 && snapshot.contains_str_at(range.end, suffix)
6206 {
6207 range.end += lookahead;
6208 }
6209 }
6210 range
6211 };
6212
6213 ranges.push(range.clone());
6214
6215 if !self.linked_edit_ranges.is_empty() {
6216 let start_anchor = snapshot.anchor_before(range.start);
6217 let end_anchor = snapshot.anchor_after(range.end);
6218 if let Some(ranges) = self
6219 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6220 {
6221 for (buffer, edits) in ranges {
6222 linked_edits
6223 .entry(buffer.clone())
6224 .or_default()
6225 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6226 }
6227 }
6228 }
6229 }
6230
6231 let common_prefix_len = old_text
6232 .chars()
6233 .zip(new_text.chars())
6234 .take_while(|(a, b)| a == b)
6235 .map(|(a, _)| a.len_utf8())
6236 .sum::<usize>();
6237
6238 cx.emit(EditorEvent::InputHandled {
6239 utf16_range_to_replace: None,
6240 text: new_text[common_prefix_len..].into(),
6241 });
6242
6243 self.transact(window, cx, |editor, window, cx| {
6244 if let Some(mut snippet) = snippet {
6245 snippet.text = new_text.to_string();
6246 editor
6247 .insert_snippet(&ranges, snippet, window, cx)
6248 .log_err();
6249 } else {
6250 editor.buffer.update(cx, |multi_buffer, cx| {
6251 let auto_indent = match completion.insert_text_mode {
6252 Some(InsertTextMode::AS_IS) => None,
6253 _ => editor.autoindent_mode.clone(),
6254 };
6255 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6256 multi_buffer.edit(edits, auto_indent, cx);
6257 });
6258 }
6259 for (buffer, edits) in linked_edits {
6260 buffer.update(cx, |buffer, cx| {
6261 let snapshot = buffer.snapshot();
6262 let edits = edits
6263 .into_iter()
6264 .map(|(range, text)| {
6265 use text::ToPoint as TP;
6266 let end_point = TP::to_point(&range.end, &snapshot);
6267 let start_point = TP::to_point(&range.start, &snapshot);
6268 (start_point..end_point, text)
6269 })
6270 .sorted_by_key(|(range, _)| range.start);
6271 buffer.edit(edits, None, cx);
6272 })
6273 }
6274
6275 editor.refresh_edit_prediction(true, false, window, cx);
6276 });
6277 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6278
6279 let show_new_completions_on_confirm = completion
6280 .confirm
6281 .as_ref()
6282 .is_some_and(|confirm| confirm(intent, window, cx));
6283 if show_new_completions_on_confirm {
6284 self.open_or_update_completions_menu(None, None, false, window, cx);
6285 }
6286
6287 let provider = self.completion_provider.as_ref()?;
6288
6289 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6290 let command = lsp_store.as_ref().and_then(|lsp_store| {
6291 let CompletionSource::Lsp {
6292 lsp_completion,
6293 server_id,
6294 ..
6295 } = &completion.source
6296 else {
6297 return None;
6298 };
6299 let lsp_command = lsp_completion.command.as_ref()?;
6300 let available_commands = lsp_store
6301 .read(cx)
6302 .lsp_server_capabilities
6303 .get(server_id)
6304 .and_then(|server_capabilities| {
6305 server_capabilities
6306 .execute_command_provider
6307 .as_ref()
6308 .map(|options| options.commands.as_slice())
6309 })?;
6310 if available_commands.contains(&lsp_command.command) {
6311 Some(CodeAction {
6312 server_id: *server_id,
6313 range: language::Anchor::MIN..language::Anchor::MIN,
6314 lsp_action: LspAction::Command(lsp_command.clone()),
6315 resolved: false,
6316 })
6317 } else {
6318 None
6319 }
6320 });
6321
6322 drop(completion);
6323 let apply_edits = provider.apply_additional_edits_for_completion(
6324 buffer_handle.clone(),
6325 completions_menu.completions.clone(),
6326 candidate_id,
6327 true,
6328 cx,
6329 );
6330
6331 let editor_settings = EditorSettings::get_global(cx);
6332 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6333 // After the code completion is finished, users often want to know what signatures are needed.
6334 // so we should automatically call signature_help
6335 self.show_signature_help(&ShowSignatureHelp, window, cx);
6336 }
6337
6338 Some(cx.spawn_in(window, async move |editor, cx| {
6339 apply_edits.await?;
6340
6341 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6342 let title = command.lsp_action.title().to_owned();
6343 let project_transaction = lsp_store
6344 .update(cx, |lsp_store, cx| {
6345 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6346 })
6347 .await
6348 .context("applying post-completion command")?;
6349 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6350 Self::open_project_transaction(
6351 &editor,
6352 workspace.downgrade(),
6353 project_transaction,
6354 title,
6355 cx,
6356 )
6357 .await?;
6358 }
6359 }
6360
6361 Ok(())
6362 }))
6363 }
6364
6365 pub fn toggle_code_actions(
6366 &mut self,
6367 action: &ToggleCodeActions,
6368 window: &mut Window,
6369 cx: &mut Context<Self>,
6370 ) {
6371 let quick_launch = action.quick_launch;
6372 let mut context_menu = self.context_menu.borrow_mut();
6373 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6374 if code_actions.deployed_from == action.deployed_from {
6375 // Toggle if we're selecting the same one
6376 *context_menu = None;
6377 cx.notify();
6378 return;
6379 } else {
6380 // Otherwise, clear it and start a new one
6381 *context_menu = None;
6382 cx.notify();
6383 }
6384 }
6385 drop(context_menu);
6386 let snapshot = self.snapshot(window, cx);
6387 let deployed_from = action.deployed_from.clone();
6388 let action = action.clone();
6389 self.completion_tasks.clear();
6390 self.discard_edit_prediction(false, cx);
6391
6392 let multibuffer_point = match &action.deployed_from {
6393 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6394 DisplayPoint::new(*row, 0).to_point(&snapshot)
6395 }
6396 _ => self
6397 .selections
6398 .newest::<Point>(&snapshot.display_snapshot)
6399 .head(),
6400 };
6401 let Some((buffer, buffer_row)) = snapshot
6402 .buffer_snapshot()
6403 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6404 .and_then(|(buffer_snapshot, range)| {
6405 self.buffer()
6406 .read(cx)
6407 .buffer(buffer_snapshot.remote_id())
6408 .map(|buffer| (buffer, range.start.row))
6409 })
6410 else {
6411 return;
6412 };
6413 let buffer_id = buffer.read(cx).remote_id();
6414 let tasks = self
6415 .tasks
6416 .get(&(buffer_id, buffer_row))
6417 .map(|t| Arc::new(t.to_owned()));
6418
6419 if !self.focus_handle.is_focused(window) {
6420 return;
6421 }
6422 let project = self.project.clone();
6423
6424 let code_actions_task = match deployed_from {
6425 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6426 _ => self.code_actions(buffer_row, window, cx),
6427 };
6428
6429 let runnable_task = match deployed_from {
6430 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6431 _ => {
6432 let mut task_context_task = Task::ready(None);
6433 if let Some(tasks) = &tasks
6434 && let Some(project) = project
6435 {
6436 task_context_task =
6437 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6438 }
6439
6440 cx.spawn_in(window, {
6441 let buffer = buffer.clone();
6442 async move |editor, cx| {
6443 let task_context = task_context_task.await;
6444
6445 let resolved_tasks =
6446 tasks
6447 .zip(task_context.clone())
6448 .map(|(tasks, task_context)| ResolvedTasks {
6449 templates: tasks.resolve(&task_context).collect(),
6450 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6451 multibuffer_point.row,
6452 tasks.column,
6453 )),
6454 });
6455 let debug_scenarios = editor
6456 .update(cx, |editor, cx| {
6457 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6458 })?
6459 .await;
6460 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6461 }
6462 })
6463 }
6464 };
6465
6466 cx.spawn_in(window, async move |editor, cx| {
6467 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6468 let code_actions = code_actions_task.await;
6469 let spawn_straight_away = quick_launch
6470 && resolved_tasks
6471 .as_ref()
6472 .is_some_and(|tasks| tasks.templates.len() == 1)
6473 && code_actions
6474 .as_ref()
6475 .is_none_or(|actions| actions.is_empty())
6476 && debug_scenarios.is_empty();
6477
6478 editor.update_in(cx, |editor, window, cx| {
6479 crate::hover_popover::hide_hover(editor, cx);
6480 let actions = CodeActionContents::new(
6481 resolved_tasks,
6482 code_actions,
6483 debug_scenarios,
6484 task_context.unwrap_or_default(),
6485 );
6486
6487 // Don't show the menu if there are no actions available
6488 if actions.is_empty() {
6489 cx.notify();
6490 return Task::ready(Ok(()));
6491 }
6492
6493 *editor.context_menu.borrow_mut() =
6494 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6495 buffer,
6496 actions,
6497 selected_item: Default::default(),
6498 scroll_handle: UniformListScrollHandle::default(),
6499 deployed_from,
6500 }));
6501 cx.notify();
6502 if spawn_straight_away
6503 && let Some(task) = editor.confirm_code_action(
6504 &ConfirmCodeAction { item_ix: Some(0) },
6505 window,
6506 cx,
6507 )
6508 {
6509 return task;
6510 }
6511
6512 Task::ready(Ok(()))
6513 })
6514 })
6515 .detach_and_log_err(cx);
6516 }
6517
6518 fn debug_scenarios(
6519 &mut self,
6520 resolved_tasks: &Option<ResolvedTasks>,
6521 buffer: &Entity<Buffer>,
6522 cx: &mut App,
6523 ) -> Task<Vec<task::DebugScenario>> {
6524 maybe!({
6525 let project = self.project()?;
6526 let dap_store = project.read(cx).dap_store();
6527 let mut scenarios = vec![];
6528 let resolved_tasks = resolved_tasks.as_ref()?;
6529 let buffer = buffer.read(cx);
6530 let language = buffer.language()?;
6531 let file = buffer.file();
6532 let debug_adapter = language_settings(language.name().into(), file, cx)
6533 .debuggers
6534 .first()
6535 .map(SharedString::from)
6536 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6537
6538 dap_store.update(cx, |dap_store, cx| {
6539 for (_, task) in &resolved_tasks.templates {
6540 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6541 task.original_task().clone(),
6542 debug_adapter.clone().into(),
6543 task.display_label().to_owned().into(),
6544 cx,
6545 );
6546 scenarios.push(maybe_scenario);
6547 }
6548 });
6549 Some(cx.background_spawn(async move {
6550 futures::future::join_all(scenarios)
6551 .await
6552 .into_iter()
6553 .flatten()
6554 .collect::<Vec<_>>()
6555 }))
6556 })
6557 .unwrap_or_else(|| Task::ready(vec![]))
6558 }
6559
6560 fn code_actions(
6561 &mut self,
6562 buffer_row: u32,
6563 window: &mut Window,
6564 cx: &mut Context<Self>,
6565 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6566 let mut task = self.code_actions_task.take();
6567 cx.spawn_in(window, async move |editor, cx| {
6568 while let Some(prev_task) = task {
6569 prev_task.await.log_err();
6570 task = editor
6571 .update(cx, |this, _| this.code_actions_task.take())
6572 .ok()?;
6573 }
6574
6575 editor
6576 .update(cx, |editor, cx| {
6577 editor
6578 .available_code_actions
6579 .clone()
6580 .and_then(|(location, code_actions)| {
6581 let snapshot = location.buffer.read(cx).snapshot();
6582 let point_range = location.range.to_point(&snapshot);
6583 let point_range = point_range.start.row..=point_range.end.row;
6584 if point_range.contains(&buffer_row) {
6585 Some(code_actions)
6586 } else {
6587 None
6588 }
6589 })
6590 })
6591 .ok()
6592 .flatten()
6593 })
6594 }
6595
6596 pub fn confirm_code_action(
6597 &mut self,
6598 action: &ConfirmCodeAction,
6599 window: &mut Window,
6600 cx: &mut Context<Self>,
6601 ) -> Option<Task<Result<()>>> {
6602 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6603
6604 let actions_menu =
6605 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6606 menu
6607 } else {
6608 return None;
6609 };
6610
6611 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6612 let action = actions_menu.actions.get(action_ix)?;
6613 let title = action.label();
6614 let buffer = actions_menu.buffer;
6615 let workspace = self.workspace()?;
6616
6617 match action {
6618 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6619 workspace.update(cx, |workspace, cx| {
6620 workspace.schedule_resolved_task(
6621 task_source_kind,
6622 resolved_task,
6623 false,
6624 window,
6625 cx,
6626 );
6627
6628 Some(Task::ready(Ok(())))
6629 })
6630 }
6631 CodeActionsItem::CodeAction {
6632 excerpt_id,
6633 action,
6634 provider,
6635 } => {
6636 let apply_code_action =
6637 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6638 let workspace = workspace.downgrade();
6639 Some(cx.spawn_in(window, async move |editor, cx| {
6640 let project_transaction = apply_code_action.await?;
6641 Self::open_project_transaction(
6642 &editor,
6643 workspace,
6644 project_transaction,
6645 title,
6646 cx,
6647 )
6648 .await
6649 }))
6650 }
6651 CodeActionsItem::DebugScenario(scenario) => {
6652 let context = actions_menu.actions.context;
6653
6654 workspace.update(cx, |workspace, cx| {
6655 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6656 workspace.start_debug_session(
6657 scenario,
6658 context,
6659 Some(buffer),
6660 None,
6661 window,
6662 cx,
6663 );
6664 });
6665 Some(Task::ready(Ok(())))
6666 }
6667 }
6668 }
6669
6670 fn open_transaction_for_hidden_buffers(
6671 workspace: Entity<Workspace>,
6672 transaction: ProjectTransaction,
6673 title: String,
6674 window: &mut Window,
6675 cx: &mut Context<Self>,
6676 ) {
6677 if transaction.0.is_empty() {
6678 return;
6679 }
6680
6681 let edited_buffers_already_open = {
6682 let other_editors: Vec<Entity<Editor>> = workspace
6683 .read(cx)
6684 .panes()
6685 .iter()
6686 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6687 .filter(|editor| editor.entity_id() != cx.entity_id())
6688 .collect();
6689
6690 transaction.0.keys().all(|buffer| {
6691 other_editors.iter().any(|editor| {
6692 let multi_buffer = editor.read(cx).buffer();
6693 multi_buffer.read(cx).is_singleton()
6694 && multi_buffer
6695 .read(cx)
6696 .as_singleton()
6697 .map_or(false, |singleton| {
6698 singleton.entity_id() == buffer.entity_id()
6699 })
6700 })
6701 })
6702 };
6703 if !edited_buffers_already_open {
6704 let workspace = workspace.downgrade();
6705 cx.defer_in(window, move |_, window, cx| {
6706 cx.spawn_in(window, async move |editor, cx| {
6707 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6708 .await
6709 .ok()
6710 })
6711 .detach();
6712 });
6713 }
6714 }
6715
6716 pub async fn open_project_transaction(
6717 editor: &WeakEntity<Editor>,
6718 workspace: WeakEntity<Workspace>,
6719 transaction: ProjectTransaction,
6720 title: String,
6721 cx: &mut AsyncWindowContext,
6722 ) -> Result<()> {
6723 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6724 cx.update(|_, cx| {
6725 entries.sort_unstable_by_key(|(buffer, _)| {
6726 buffer.read(cx).file().map(|f| f.path().clone())
6727 });
6728 })?;
6729 if entries.is_empty() {
6730 return Ok(());
6731 }
6732
6733 // If the project transaction's edits are all contained within this editor, then
6734 // avoid opening a new editor to display them.
6735
6736 if let [(buffer, transaction)] = &*entries {
6737 let excerpt = editor.update(cx, |editor, cx| {
6738 editor
6739 .buffer()
6740 .read(cx)
6741 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6742 })?;
6743 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6744 && excerpted_buffer == *buffer
6745 {
6746 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6747 let excerpt_range = excerpt_range.to_offset(buffer);
6748 buffer
6749 .edited_ranges_for_transaction::<usize>(transaction)
6750 .all(|range| {
6751 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6752 })
6753 });
6754
6755 if all_edits_within_excerpt {
6756 return Ok(());
6757 }
6758 }
6759 }
6760
6761 let mut ranges_to_highlight = Vec::new();
6762 let excerpt_buffer = cx.new(|cx| {
6763 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6764 for (buffer_handle, transaction) in &entries {
6765 let edited_ranges = buffer_handle
6766 .read(cx)
6767 .edited_ranges_for_transaction::<Point>(transaction)
6768 .collect::<Vec<_>>();
6769 let (ranges, _) = multibuffer.set_excerpts_for_path(
6770 PathKey::for_buffer(buffer_handle, cx),
6771 buffer_handle.clone(),
6772 edited_ranges,
6773 multibuffer_context_lines(cx),
6774 cx,
6775 );
6776
6777 ranges_to_highlight.extend(ranges);
6778 }
6779 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6780 multibuffer
6781 });
6782
6783 workspace.update_in(cx, |workspace, window, cx| {
6784 let project = workspace.project().clone();
6785 let editor =
6786 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6787 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6788 editor.update(cx, |editor, cx| {
6789 editor.highlight_background::<Self>(
6790 &ranges_to_highlight,
6791 |_, theme| theme.colors().editor_highlighted_line_background,
6792 cx,
6793 );
6794 });
6795 })?;
6796
6797 Ok(())
6798 }
6799
6800 pub fn clear_code_action_providers(&mut self) {
6801 self.code_action_providers.clear();
6802 self.available_code_actions.take();
6803 }
6804
6805 pub fn add_code_action_provider(
6806 &mut self,
6807 provider: Rc<dyn CodeActionProvider>,
6808 window: &mut Window,
6809 cx: &mut Context<Self>,
6810 ) {
6811 if self
6812 .code_action_providers
6813 .iter()
6814 .any(|existing_provider| existing_provider.id() == provider.id())
6815 {
6816 return;
6817 }
6818
6819 self.code_action_providers.push(provider);
6820 self.refresh_code_actions(window, cx);
6821 }
6822
6823 pub fn remove_code_action_provider(
6824 &mut self,
6825 id: Arc<str>,
6826 window: &mut Window,
6827 cx: &mut Context<Self>,
6828 ) {
6829 self.code_action_providers
6830 .retain(|provider| provider.id() != id);
6831 self.refresh_code_actions(window, cx);
6832 }
6833
6834 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6835 !self.code_action_providers.is_empty()
6836 && EditorSettings::get_global(cx).toolbar.code_actions
6837 }
6838
6839 pub fn has_available_code_actions(&self) -> bool {
6840 self.available_code_actions
6841 .as_ref()
6842 .is_some_and(|(_, actions)| !actions.is_empty())
6843 }
6844
6845 fn render_inline_code_actions(
6846 &self,
6847 icon_size: ui::IconSize,
6848 display_row: DisplayRow,
6849 is_active: bool,
6850 cx: &mut Context<Self>,
6851 ) -> AnyElement {
6852 let show_tooltip = !self.context_menu_visible();
6853 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6854 .icon_size(icon_size)
6855 .shape(ui::IconButtonShape::Square)
6856 .icon_color(ui::Color::Hidden)
6857 .toggle_state(is_active)
6858 .when(show_tooltip, |this| {
6859 this.tooltip({
6860 let focus_handle = self.focus_handle.clone();
6861 move |_window, cx| {
6862 Tooltip::for_action_in(
6863 "Toggle Code Actions",
6864 &ToggleCodeActions {
6865 deployed_from: None,
6866 quick_launch: false,
6867 },
6868 &focus_handle,
6869 cx,
6870 )
6871 }
6872 })
6873 })
6874 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6875 window.focus(&editor.focus_handle(cx), cx);
6876 editor.toggle_code_actions(
6877 &crate::actions::ToggleCodeActions {
6878 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6879 display_row,
6880 )),
6881 quick_launch: false,
6882 },
6883 window,
6884 cx,
6885 );
6886 }))
6887 .into_any_element()
6888 }
6889
6890 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6891 &self.context_menu
6892 }
6893
6894 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6895 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6896 cx.background_executor()
6897 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6898 .await;
6899
6900 let (start_buffer, start, _, end, newest_selection) = this
6901 .update(cx, |this, cx| {
6902 let newest_selection = this.selections.newest_anchor().clone();
6903 if newest_selection.head().diff_base_anchor.is_some() {
6904 return None;
6905 }
6906 let display_snapshot = this.display_snapshot(cx);
6907 let newest_selection_adjusted =
6908 this.selections.newest_adjusted(&display_snapshot);
6909 let buffer = this.buffer.read(cx);
6910
6911 let (start_buffer, start) =
6912 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6913 let (end_buffer, end) =
6914 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6915
6916 Some((start_buffer, start, end_buffer, end, newest_selection))
6917 })?
6918 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6919 .context(
6920 "Expected selection to lie in a single buffer when refreshing code actions",
6921 )?;
6922 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6923 let providers = this.code_action_providers.clone();
6924 let tasks = this
6925 .code_action_providers
6926 .iter()
6927 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6928 .collect::<Vec<_>>();
6929 (providers, tasks)
6930 })?;
6931
6932 let mut actions = Vec::new();
6933 for (provider, provider_actions) in
6934 providers.into_iter().zip(future::join_all(tasks).await)
6935 {
6936 if let Some(provider_actions) = provider_actions.log_err() {
6937 actions.extend(provider_actions.into_iter().map(|action| {
6938 AvailableCodeAction {
6939 excerpt_id: newest_selection.start.excerpt_id,
6940 action,
6941 provider: provider.clone(),
6942 }
6943 }));
6944 }
6945 }
6946
6947 this.update(cx, |this, cx| {
6948 this.available_code_actions = if actions.is_empty() {
6949 None
6950 } else {
6951 Some((
6952 Location {
6953 buffer: start_buffer,
6954 range: start..end,
6955 },
6956 actions.into(),
6957 ))
6958 };
6959 cx.notify();
6960 })
6961 }));
6962 }
6963
6964 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6965 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6966 self.show_git_blame_inline = false;
6967
6968 self.show_git_blame_inline_delay_task =
6969 Some(cx.spawn_in(window, async move |this, cx| {
6970 cx.background_executor().timer(delay).await;
6971
6972 this.update(cx, |this, cx| {
6973 this.show_git_blame_inline = true;
6974 cx.notify();
6975 })
6976 .log_err();
6977 }));
6978 }
6979 }
6980
6981 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6982 let snapshot = self.snapshot(window, cx);
6983 let cursor = self
6984 .selections
6985 .newest::<Point>(&snapshot.display_snapshot)
6986 .head();
6987 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6988 else {
6989 return;
6990 };
6991
6992 if self.blame.is_none() {
6993 self.start_git_blame(true, window, cx);
6994 }
6995 let Some(blame) = self.blame.as_ref() else {
6996 return;
6997 };
6998
6999 let row_info = RowInfo {
7000 buffer_id: Some(buffer.remote_id()),
7001 buffer_row: Some(point.row),
7002 ..Default::default()
7003 };
7004 let Some((buffer, blame_entry)) = blame
7005 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7006 .flatten()
7007 else {
7008 return;
7009 };
7010
7011 let anchor = self.selections.newest_anchor().head();
7012 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7013 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7014 self.show_blame_popover(
7015 buffer,
7016 &blame_entry,
7017 position + last_bounds.origin,
7018 true,
7019 cx,
7020 );
7021 };
7022 }
7023
7024 fn show_blame_popover(
7025 &mut self,
7026 buffer: BufferId,
7027 blame_entry: &BlameEntry,
7028 position: gpui::Point<Pixels>,
7029 ignore_timeout: bool,
7030 cx: &mut Context<Self>,
7031 ) {
7032 if let Some(state) = &mut self.inline_blame_popover {
7033 state.hide_task.take();
7034 } else {
7035 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7036 let blame_entry = blame_entry.clone();
7037 let show_task = cx.spawn(async move |editor, cx| {
7038 if !ignore_timeout {
7039 cx.background_executor()
7040 .timer(std::time::Duration::from_millis(blame_popover_delay))
7041 .await;
7042 }
7043 editor
7044 .update(cx, |editor, cx| {
7045 editor.inline_blame_popover_show_task.take();
7046 let Some(blame) = editor.blame.as_ref() else {
7047 return;
7048 };
7049 let blame = blame.read(cx);
7050 let details = blame.details_for_entry(buffer, &blame_entry);
7051 let markdown = cx.new(|cx| {
7052 Markdown::new(
7053 details
7054 .as_ref()
7055 .map(|message| message.message.clone())
7056 .unwrap_or_default(),
7057 None,
7058 None,
7059 cx,
7060 )
7061 });
7062 editor.inline_blame_popover = Some(InlineBlamePopover {
7063 position,
7064 hide_task: None,
7065 popover_bounds: None,
7066 popover_state: InlineBlamePopoverState {
7067 scroll_handle: ScrollHandle::new(),
7068 commit_message: details,
7069 markdown,
7070 },
7071 keyboard_grace: ignore_timeout,
7072 });
7073 cx.notify();
7074 })
7075 .ok();
7076 });
7077 self.inline_blame_popover_show_task = Some(show_task);
7078 }
7079 }
7080
7081 pub fn has_mouse_context_menu(&self) -> bool {
7082 self.mouse_context_menu.is_some()
7083 }
7084
7085 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7086 self.inline_blame_popover_show_task.take();
7087 if let Some(state) = &mut self.inline_blame_popover {
7088 let hide_task = cx.spawn(async move |editor, cx| {
7089 if !ignore_timeout {
7090 cx.background_executor()
7091 .timer(std::time::Duration::from_millis(100))
7092 .await;
7093 }
7094 editor
7095 .update(cx, |editor, cx| {
7096 editor.inline_blame_popover.take();
7097 cx.notify();
7098 })
7099 .ok();
7100 });
7101 state.hide_task = Some(hide_task);
7102 true
7103 } else {
7104 false
7105 }
7106 }
7107
7108 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7109 if self.pending_rename.is_some() {
7110 return None;
7111 }
7112
7113 let provider = self.semantics_provider.clone()?;
7114 let buffer = self.buffer.read(cx);
7115 let newest_selection = self.selections.newest_anchor().clone();
7116 let cursor_position = newest_selection.head();
7117 let (cursor_buffer, cursor_buffer_position) =
7118 buffer.text_anchor_for_position(cursor_position, cx)?;
7119 let (tail_buffer, tail_buffer_position) =
7120 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7121 if cursor_buffer != tail_buffer {
7122 return None;
7123 }
7124
7125 let snapshot = cursor_buffer.read(cx).snapshot();
7126 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7127 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7128 if start_word_range != end_word_range {
7129 self.document_highlights_task.take();
7130 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7131 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7132 return None;
7133 }
7134
7135 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7136 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7137 cx.background_executor()
7138 .timer(Duration::from_millis(debounce))
7139 .await;
7140
7141 let highlights = if let Some(highlights) = cx.update(|cx| {
7142 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7143 }) {
7144 highlights.await.log_err()
7145 } else {
7146 None
7147 };
7148
7149 if let Some(highlights) = highlights {
7150 this.update(cx, |this, cx| {
7151 if this.pending_rename.is_some() {
7152 return;
7153 }
7154
7155 let buffer = this.buffer.read(cx);
7156 if buffer
7157 .text_anchor_for_position(cursor_position, cx)
7158 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7159 {
7160 return;
7161 }
7162
7163 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7164 let mut write_ranges = Vec::new();
7165 let mut read_ranges = Vec::new();
7166 for highlight in highlights {
7167 let buffer_id = cursor_buffer.read(cx).remote_id();
7168 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7169 {
7170 let start = highlight
7171 .range
7172 .start
7173 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7174 let end = highlight
7175 .range
7176 .end
7177 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7178 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7179 continue;
7180 }
7181
7182 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7183 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7184 write_ranges.push(range);
7185 } else {
7186 read_ranges.push(range);
7187 }
7188 }
7189 }
7190
7191 this.highlight_background::<DocumentHighlightRead>(
7192 &read_ranges,
7193 |_, theme| theme.colors().editor_document_highlight_read_background,
7194 cx,
7195 );
7196 this.highlight_background::<DocumentHighlightWrite>(
7197 &write_ranges,
7198 |_, theme| theme.colors().editor_document_highlight_write_background,
7199 cx,
7200 );
7201 cx.notify();
7202 })
7203 .log_err();
7204 }
7205 }));
7206 None
7207 }
7208
7209 fn prepare_highlight_query_from_selection(
7210 &mut self,
7211 window: &Window,
7212 cx: &mut Context<Editor>,
7213 ) -> Option<(String, Range<Anchor>)> {
7214 if matches!(self.mode, EditorMode::SingleLine) {
7215 return None;
7216 }
7217 if !EditorSettings::get_global(cx).selection_highlight {
7218 return None;
7219 }
7220 if self.selections.count() != 1 || self.selections.line_mode() {
7221 return None;
7222 }
7223 let snapshot = self.snapshot(window, cx);
7224 let selection = self.selections.newest::<Point>(&snapshot);
7225 // If the selection spans multiple rows OR it is empty
7226 if selection.start.row != selection.end.row
7227 || selection.start.column == selection.end.column
7228 {
7229 return None;
7230 }
7231 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7232 let query = snapshot
7233 .buffer_snapshot()
7234 .text_for_range(selection_anchor_range.clone())
7235 .collect::<String>();
7236 if query.trim().is_empty() {
7237 return None;
7238 }
7239 Some((query, selection_anchor_range))
7240 }
7241
7242 #[ztracing::instrument(skip_all)]
7243 fn update_selection_occurrence_highlights(
7244 &mut self,
7245 query_text: String,
7246 query_range: Range<Anchor>,
7247 multi_buffer_range_to_query: Range<Point>,
7248 use_debounce: bool,
7249 window: &mut Window,
7250 cx: &mut Context<Editor>,
7251 ) -> Task<()> {
7252 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7253 cx.spawn_in(window, async move |editor, cx| {
7254 if use_debounce {
7255 cx.background_executor()
7256 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7257 .await;
7258 }
7259 let match_task = cx.background_spawn(async move {
7260 let buffer_ranges = multi_buffer_snapshot
7261 .range_to_buffer_ranges(multi_buffer_range_to_query)
7262 .into_iter()
7263 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7264 let mut match_ranges = Vec::new();
7265 let Ok(regex) = project::search::SearchQuery::text(
7266 query_text.clone(),
7267 false,
7268 false,
7269 false,
7270 Default::default(),
7271 Default::default(),
7272 false,
7273 None,
7274 ) else {
7275 return Vec::default();
7276 };
7277 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7278 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7279 match_ranges.extend(
7280 regex
7281 .search(
7282 buffer_snapshot,
7283 Some(search_range.start.0..search_range.end.0),
7284 )
7285 .await
7286 .into_iter()
7287 .filter_map(|match_range| {
7288 let match_start = buffer_snapshot
7289 .anchor_after(search_range.start + match_range.start);
7290 let match_end = buffer_snapshot
7291 .anchor_before(search_range.start + match_range.end);
7292 let match_anchor_range =
7293 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7294 (match_anchor_range != query_range).then_some(match_anchor_range)
7295 }),
7296 );
7297 }
7298 match_ranges
7299 });
7300 let match_ranges = match_task.await;
7301 editor
7302 .update_in(cx, |editor, _, cx| {
7303 if use_debounce {
7304 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7305 editor.debounced_selection_highlight_complete = true;
7306 } else if editor.debounced_selection_highlight_complete {
7307 return;
7308 }
7309 if !match_ranges.is_empty() {
7310 editor.highlight_background::<SelectedTextHighlight>(
7311 &match_ranges,
7312 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7313 cx,
7314 )
7315 }
7316 })
7317 .log_err();
7318 })
7319 }
7320
7321 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7322 struct NewlineFold;
7323 let type_id = std::any::TypeId::of::<NewlineFold>();
7324 if !self.mode.is_single_line() {
7325 return;
7326 }
7327 let snapshot = self.snapshot(window, cx);
7328 if snapshot.buffer_snapshot().max_point().row == 0 {
7329 return;
7330 }
7331 let task = cx.background_spawn(async move {
7332 let new_newlines = snapshot
7333 .buffer_chars_at(MultiBufferOffset(0))
7334 .filter_map(|(c, i)| {
7335 if c == '\n' {
7336 Some(
7337 snapshot.buffer_snapshot().anchor_after(i)
7338 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7339 )
7340 } else {
7341 None
7342 }
7343 })
7344 .collect::<Vec<_>>();
7345 let existing_newlines = snapshot
7346 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7347 .filter_map(|fold| {
7348 if fold.placeholder.type_tag == Some(type_id) {
7349 Some(fold.range.start..fold.range.end)
7350 } else {
7351 None
7352 }
7353 })
7354 .collect::<Vec<_>>();
7355
7356 (new_newlines, existing_newlines)
7357 });
7358 self.folding_newlines = cx.spawn(async move |this, cx| {
7359 let (new_newlines, existing_newlines) = task.await;
7360 if new_newlines == existing_newlines {
7361 return;
7362 }
7363 let placeholder = FoldPlaceholder {
7364 render: Arc::new(move |_, _, cx| {
7365 div()
7366 .bg(cx.theme().status().hint_background)
7367 .border_b_1()
7368 .size_full()
7369 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7370 .border_color(cx.theme().status().hint)
7371 .child("\\n")
7372 .into_any()
7373 }),
7374 constrain_width: false,
7375 merge_adjacent: false,
7376 type_tag: Some(type_id),
7377 };
7378 let creases = new_newlines
7379 .into_iter()
7380 .map(|range| Crease::simple(range, placeholder.clone()))
7381 .collect();
7382 this.update(cx, |this, cx| {
7383 this.display_map.update(cx, |display_map, cx| {
7384 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7385 display_map.fold(creases, cx);
7386 });
7387 })
7388 .ok();
7389 });
7390 }
7391
7392 #[ztracing::instrument(skip_all)]
7393 fn refresh_selected_text_highlights(
7394 &mut self,
7395 on_buffer_edit: bool,
7396 window: &mut Window,
7397 cx: &mut Context<Editor>,
7398 ) {
7399 let Some((query_text, query_range)) =
7400 self.prepare_highlight_query_from_selection(window, cx)
7401 else {
7402 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7403 self.quick_selection_highlight_task.take();
7404 self.debounced_selection_highlight_task.take();
7405 self.debounced_selection_highlight_complete = false;
7406 return;
7407 };
7408 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7409 let query_changed = self
7410 .quick_selection_highlight_task
7411 .as_ref()
7412 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7413 if query_changed {
7414 self.debounced_selection_highlight_complete = false;
7415 }
7416 if on_buffer_edit || query_changed {
7417 let multi_buffer_visible_start = self
7418 .scroll_manager
7419 .anchor()
7420 .anchor
7421 .to_point(&multi_buffer_snapshot);
7422 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7423 multi_buffer_visible_start
7424 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7425 Bias::Left,
7426 );
7427 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7428 self.quick_selection_highlight_task = Some((
7429 query_range.clone(),
7430 self.update_selection_occurrence_highlights(
7431 query_text.clone(),
7432 query_range.clone(),
7433 multi_buffer_visible_range,
7434 false,
7435 window,
7436 cx,
7437 ),
7438 ));
7439 }
7440 if on_buffer_edit
7441 || self
7442 .debounced_selection_highlight_task
7443 .as_ref()
7444 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7445 {
7446 let multi_buffer_start = multi_buffer_snapshot
7447 .anchor_before(MultiBufferOffset(0))
7448 .to_point(&multi_buffer_snapshot);
7449 let multi_buffer_end = multi_buffer_snapshot
7450 .anchor_after(multi_buffer_snapshot.len())
7451 .to_point(&multi_buffer_snapshot);
7452 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7453 self.debounced_selection_highlight_task = Some((
7454 query_range.clone(),
7455 self.update_selection_occurrence_highlights(
7456 query_text,
7457 query_range,
7458 multi_buffer_full_range,
7459 true,
7460 window,
7461 cx,
7462 ),
7463 ));
7464 }
7465 }
7466
7467 pub fn refresh_edit_prediction(
7468 &mut self,
7469 debounce: bool,
7470 user_requested: bool,
7471 window: &mut Window,
7472 cx: &mut Context<Self>,
7473 ) -> Option<()> {
7474 if DisableAiSettings::get_global(cx).disable_ai {
7475 return None;
7476 }
7477
7478 let provider = self.edit_prediction_provider()?;
7479 let cursor = self.selections.newest_anchor().head();
7480 let (buffer, cursor_buffer_position) =
7481 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7482
7483 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7484 self.discard_edit_prediction(false, cx);
7485 return None;
7486 }
7487
7488 self.update_visible_edit_prediction(window, cx);
7489
7490 if !user_requested
7491 && (!self.should_show_edit_predictions()
7492 || !self.is_focused(window)
7493 || buffer.read(cx).is_empty())
7494 {
7495 self.discard_edit_prediction(false, cx);
7496 return None;
7497 }
7498
7499 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7500 Some(())
7501 }
7502
7503 fn show_edit_predictions_in_menu(&self) -> bool {
7504 match self.edit_prediction_settings {
7505 EditPredictionSettings::Disabled => false,
7506 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7507 }
7508 }
7509
7510 pub fn edit_predictions_enabled(&self) -> bool {
7511 match self.edit_prediction_settings {
7512 EditPredictionSettings::Disabled => false,
7513 EditPredictionSettings::Enabled { .. } => true,
7514 }
7515 }
7516
7517 fn edit_prediction_requires_modifier(&self) -> bool {
7518 match self.edit_prediction_settings {
7519 EditPredictionSettings::Disabled => false,
7520 EditPredictionSettings::Enabled {
7521 preview_requires_modifier,
7522 ..
7523 } => preview_requires_modifier,
7524 }
7525 }
7526
7527 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7528 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7529 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7530 self.discard_edit_prediction(false, cx);
7531 } else {
7532 let selection = self.selections.newest_anchor();
7533 let cursor = selection.head();
7534
7535 if let Some((buffer, cursor_buffer_position)) =
7536 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7537 {
7538 self.edit_prediction_settings =
7539 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7540 }
7541 }
7542 }
7543
7544 fn edit_prediction_settings_at_position(
7545 &self,
7546 buffer: &Entity<Buffer>,
7547 buffer_position: language::Anchor,
7548 cx: &App,
7549 ) -> EditPredictionSettings {
7550 if !self.mode.is_full()
7551 || !self.show_edit_predictions_override.unwrap_or(true)
7552 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7553 {
7554 return EditPredictionSettings::Disabled;
7555 }
7556
7557 let buffer = buffer.read(cx);
7558
7559 let file = buffer.file();
7560
7561 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7562 return EditPredictionSettings::Disabled;
7563 };
7564
7565 let by_provider = matches!(
7566 self.menu_edit_predictions_policy,
7567 MenuEditPredictionsPolicy::ByProvider
7568 );
7569
7570 let show_in_menu = by_provider
7571 && self
7572 .edit_prediction_provider
7573 .as_ref()
7574 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7575
7576 let preview_requires_modifier =
7577 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7578
7579 EditPredictionSettings::Enabled {
7580 show_in_menu,
7581 preview_requires_modifier,
7582 }
7583 }
7584
7585 fn should_show_edit_predictions(&self) -> bool {
7586 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7587 }
7588
7589 pub fn edit_prediction_preview_is_active(&self) -> bool {
7590 matches!(
7591 self.edit_prediction_preview,
7592 EditPredictionPreview::Active { .. }
7593 )
7594 }
7595
7596 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7597 let cursor = self.selections.newest_anchor().head();
7598 if let Some((buffer, cursor_position)) =
7599 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7600 {
7601 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7602 } else {
7603 false
7604 }
7605 }
7606
7607 pub fn supports_minimap(&self, cx: &App) -> bool {
7608 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7609 }
7610
7611 fn edit_predictions_enabled_in_buffer(
7612 &self,
7613 buffer: &Entity<Buffer>,
7614 buffer_position: language::Anchor,
7615 cx: &App,
7616 ) -> bool {
7617 maybe!({
7618 if self.read_only(cx) {
7619 return Some(false);
7620 }
7621 let provider = self.edit_prediction_provider()?;
7622 if !provider.is_enabled(buffer, buffer_position, cx) {
7623 return Some(false);
7624 }
7625 let buffer = buffer.read(cx);
7626 let Some(file) = buffer.file() else {
7627 return Some(true);
7628 };
7629 let settings = all_language_settings(Some(file), cx);
7630 Some(settings.edit_predictions_enabled_for_file(file, cx))
7631 })
7632 .unwrap_or(false)
7633 }
7634
7635 pub fn show_edit_prediction(
7636 &mut self,
7637 _: &ShowEditPrediction,
7638 window: &mut Window,
7639 cx: &mut Context<Self>,
7640 ) {
7641 if !self.has_active_edit_prediction() {
7642 self.refresh_edit_prediction(false, true, window, cx);
7643 return;
7644 }
7645
7646 self.update_visible_edit_prediction(window, cx);
7647 }
7648
7649 pub fn display_cursor_names(
7650 &mut self,
7651 _: &DisplayCursorNames,
7652 window: &mut Window,
7653 cx: &mut Context<Self>,
7654 ) {
7655 self.show_cursor_names(window, cx);
7656 }
7657
7658 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7659 self.show_cursor_names = true;
7660 cx.notify();
7661 cx.spawn_in(window, async move |this, cx| {
7662 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7663 this.update(cx, |this, cx| {
7664 this.show_cursor_names = false;
7665 cx.notify()
7666 })
7667 .ok()
7668 })
7669 .detach();
7670 }
7671
7672 pub fn accept_partial_edit_prediction(
7673 &mut self,
7674 granularity: EditPredictionGranularity,
7675 window: &mut Window,
7676 cx: &mut Context<Self>,
7677 ) {
7678 if self.show_edit_predictions_in_menu() {
7679 self.hide_context_menu(window, cx);
7680 }
7681
7682 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7683 return;
7684 };
7685
7686 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7687 return;
7688 }
7689
7690 match &active_edit_prediction.completion {
7691 EditPrediction::MoveWithin { target, .. } => {
7692 let target = *target;
7693
7694 if matches!(granularity, EditPredictionGranularity::Full) {
7695 if let Some(position_map) = &self.last_position_map {
7696 let target_row = target.to_display_point(&position_map.snapshot).row();
7697 let is_visible = position_map.visible_row_range.contains(&target_row);
7698
7699 if is_visible || !self.edit_prediction_requires_modifier() {
7700 self.unfold_ranges(&[target..target], true, false, cx);
7701 self.change_selections(
7702 SelectionEffects::scroll(Autoscroll::newest()),
7703 window,
7704 cx,
7705 |selections| {
7706 selections.select_anchor_ranges([target..target]);
7707 },
7708 );
7709 self.clear_row_highlights::<EditPredictionPreview>();
7710 self.edit_prediction_preview
7711 .set_previous_scroll_position(None);
7712 } else {
7713 // Highlight and request scroll
7714 self.edit_prediction_preview
7715 .set_previous_scroll_position(Some(
7716 position_map.snapshot.scroll_anchor,
7717 ));
7718 self.highlight_rows::<EditPredictionPreview>(
7719 target..target,
7720 cx.theme().colors().editor_highlighted_line_background,
7721 RowHighlightOptions {
7722 autoscroll: true,
7723 ..Default::default()
7724 },
7725 cx,
7726 );
7727 self.request_autoscroll(Autoscroll::fit(), cx);
7728 }
7729 }
7730 } else {
7731 self.change_selections(
7732 SelectionEffects::scroll(Autoscroll::newest()),
7733 window,
7734 cx,
7735 |selections| {
7736 selections.select_anchor_ranges([target..target]);
7737 },
7738 );
7739 }
7740 }
7741 EditPrediction::MoveOutside { snapshot, target } => {
7742 if let Some(workspace) = self.workspace() {
7743 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7744 .detach_and_log_err(cx);
7745 }
7746 }
7747 EditPrediction::Edit { edits, .. } => {
7748 self.report_edit_prediction_event(
7749 active_edit_prediction.completion_id.clone(),
7750 true,
7751 cx,
7752 );
7753
7754 match granularity {
7755 EditPredictionGranularity::Full => {
7756 if let Some(provider) = self.edit_prediction_provider() {
7757 provider.accept(cx);
7758 }
7759
7760 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7761 let snapshot = self.buffer.read(cx).snapshot(cx);
7762 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7763
7764 self.buffer.update(cx, |buffer, cx| {
7765 buffer.edit(edits.iter().cloned(), None, cx)
7766 });
7767
7768 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7769 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7770 });
7771
7772 let selections = self.selections.disjoint_anchors_arc();
7773 if let Some(transaction_id_now) =
7774 self.buffer.read(cx).last_transaction_id(cx)
7775 {
7776 if transaction_id_prev != Some(transaction_id_now) {
7777 self.selection_history
7778 .insert_transaction(transaction_id_now, selections);
7779 }
7780 }
7781
7782 self.update_visible_edit_prediction(window, cx);
7783 if self.active_edit_prediction.is_none() {
7784 self.refresh_edit_prediction(true, true, window, cx);
7785 }
7786 cx.notify();
7787 }
7788 _ => {
7789 let snapshot = self.buffer.read(cx).snapshot(cx);
7790 let cursor_offset = self
7791 .selections
7792 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7793 .head();
7794
7795 let insertion = edits.iter().find_map(|(range, text)| {
7796 let range = range.to_offset(&snapshot);
7797 if range.is_empty() && range.start == cursor_offset {
7798 Some(text)
7799 } else {
7800 None
7801 }
7802 });
7803
7804 if let Some(text) = insertion {
7805 let text_to_insert = match granularity {
7806 EditPredictionGranularity::Word => {
7807 let mut partial = text
7808 .chars()
7809 .by_ref()
7810 .take_while(|c| c.is_alphabetic())
7811 .collect::<String>();
7812 if partial.is_empty() {
7813 partial = text
7814 .chars()
7815 .by_ref()
7816 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7817 .collect::<String>();
7818 }
7819 partial
7820 }
7821 EditPredictionGranularity::Line => {
7822 if let Some(line) = text.split_inclusive('\n').next() {
7823 line.to_string()
7824 } else {
7825 text.to_string()
7826 }
7827 }
7828 EditPredictionGranularity::Full => unreachable!(),
7829 };
7830
7831 cx.emit(EditorEvent::InputHandled {
7832 utf16_range_to_replace: None,
7833 text: text_to_insert.clone().into(),
7834 });
7835
7836 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7837 self.refresh_edit_prediction(true, true, window, cx);
7838 cx.notify();
7839 } else {
7840 self.accept_partial_edit_prediction(
7841 EditPredictionGranularity::Full,
7842 window,
7843 cx,
7844 );
7845 }
7846 }
7847 }
7848 }
7849 }
7850
7851 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7852 }
7853
7854 pub fn accept_next_word_edit_prediction(
7855 &mut self,
7856 _: &AcceptNextWordEditPrediction,
7857 window: &mut Window,
7858 cx: &mut Context<Self>,
7859 ) {
7860 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7861 }
7862
7863 pub fn accept_next_line_edit_prediction(
7864 &mut self,
7865 _: &AcceptNextLineEditPrediction,
7866 window: &mut Window,
7867 cx: &mut Context<Self>,
7868 ) {
7869 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7870 }
7871
7872 pub fn accept_edit_prediction(
7873 &mut self,
7874 _: &AcceptEditPrediction,
7875 window: &mut Window,
7876 cx: &mut Context<Self>,
7877 ) {
7878 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7879 }
7880
7881 fn discard_edit_prediction(
7882 &mut self,
7883 should_report_edit_prediction_event: bool,
7884 cx: &mut Context<Self>,
7885 ) -> bool {
7886 if should_report_edit_prediction_event {
7887 let completion_id = self
7888 .active_edit_prediction
7889 .as_ref()
7890 .and_then(|active_completion| active_completion.completion_id.clone());
7891
7892 self.report_edit_prediction_event(completion_id, false, cx);
7893 }
7894
7895 if let Some(provider) = self.edit_prediction_provider() {
7896 provider.discard(cx);
7897 }
7898
7899 self.take_active_edit_prediction(cx)
7900 }
7901
7902 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7903 let Some(provider) = self.edit_prediction_provider() else {
7904 return;
7905 };
7906
7907 let Some((_, buffer, _)) = self
7908 .buffer
7909 .read(cx)
7910 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7911 else {
7912 return;
7913 };
7914
7915 let extension = buffer
7916 .read(cx)
7917 .file()
7918 .and_then(|file| Some(file.path().extension()?.to_string()));
7919
7920 let event_type = match accepted {
7921 true => "Edit Prediction Accepted",
7922 false => "Edit Prediction Discarded",
7923 };
7924 telemetry::event!(
7925 event_type,
7926 provider = provider.name(),
7927 prediction_id = id,
7928 suggestion_accepted = accepted,
7929 file_extension = extension,
7930 );
7931 }
7932
7933 fn open_editor_at_anchor(
7934 snapshot: &language::BufferSnapshot,
7935 target: language::Anchor,
7936 workspace: &Entity<Workspace>,
7937 window: &mut Window,
7938 cx: &mut App,
7939 ) -> Task<Result<()>> {
7940 workspace.update(cx, |workspace, cx| {
7941 let path = snapshot.file().map(|file| file.full_path(cx));
7942 let Some(path) =
7943 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7944 else {
7945 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7946 };
7947 let target = text::ToPoint::to_point(&target, snapshot);
7948 let item = workspace.open_path(path, None, true, window, cx);
7949 window.spawn(cx, async move |cx| {
7950 let Some(editor) = item.await?.downcast::<Editor>() else {
7951 return Ok(());
7952 };
7953 editor
7954 .update_in(cx, |editor, window, cx| {
7955 editor.go_to_singleton_buffer_point(target, window, cx);
7956 })
7957 .ok();
7958 anyhow::Ok(())
7959 })
7960 })
7961 }
7962
7963 pub fn has_active_edit_prediction(&self) -> bool {
7964 self.active_edit_prediction.is_some()
7965 }
7966
7967 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7968 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7969 return false;
7970 };
7971
7972 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7973 self.clear_highlights::<EditPredictionHighlight>(cx);
7974 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7975 true
7976 }
7977
7978 /// Returns true when we're displaying the edit prediction popover below the cursor
7979 /// like we are not previewing and the LSP autocomplete menu is visible
7980 /// or we are in `when_holding_modifier` mode.
7981 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7982 if self.edit_prediction_preview_is_active()
7983 || !self.show_edit_predictions_in_menu()
7984 || !self.edit_predictions_enabled()
7985 {
7986 return false;
7987 }
7988
7989 if self.has_visible_completions_menu() {
7990 return true;
7991 }
7992
7993 has_completion && self.edit_prediction_requires_modifier()
7994 }
7995
7996 fn handle_modifiers_changed(
7997 &mut self,
7998 modifiers: Modifiers,
7999 position_map: &PositionMap,
8000 window: &mut Window,
8001 cx: &mut Context<Self>,
8002 ) {
8003 // Ensure that the edit prediction preview is updated, even when not
8004 // enabled, if there's an active edit prediction preview.
8005 if self.show_edit_predictions_in_menu()
8006 || matches!(
8007 self.edit_prediction_preview,
8008 EditPredictionPreview::Active { .. }
8009 )
8010 {
8011 self.update_edit_prediction_preview(&modifiers, window, cx);
8012 }
8013
8014 self.update_selection_mode(&modifiers, position_map, window, cx);
8015
8016 let mouse_position = window.mouse_position();
8017 if !position_map.text_hitbox.is_hovered(window) {
8018 return;
8019 }
8020
8021 self.update_hovered_link(
8022 position_map.point_for_position(mouse_position),
8023 &position_map.snapshot,
8024 modifiers,
8025 window,
8026 cx,
8027 )
8028 }
8029
8030 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8031 match EditorSettings::get_global(cx).multi_cursor_modifier {
8032 MultiCursorModifier::Alt => modifiers.secondary(),
8033 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8034 }
8035 }
8036
8037 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8038 match EditorSettings::get_global(cx).multi_cursor_modifier {
8039 MultiCursorModifier::Alt => modifiers.alt,
8040 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8041 }
8042 }
8043
8044 fn columnar_selection_mode(
8045 modifiers: &Modifiers,
8046 cx: &mut Context<Self>,
8047 ) -> Option<ColumnarMode> {
8048 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8049 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8050 Some(ColumnarMode::FromMouse)
8051 } else if Self::is_alt_pressed(modifiers, cx) {
8052 Some(ColumnarMode::FromSelection)
8053 } else {
8054 None
8055 }
8056 } else {
8057 None
8058 }
8059 }
8060
8061 fn update_selection_mode(
8062 &mut self,
8063 modifiers: &Modifiers,
8064 position_map: &PositionMap,
8065 window: &mut Window,
8066 cx: &mut Context<Self>,
8067 ) {
8068 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8069 return;
8070 };
8071 if self.selections.pending_anchor().is_none() {
8072 return;
8073 }
8074
8075 let mouse_position = window.mouse_position();
8076 let point_for_position = position_map.point_for_position(mouse_position);
8077 let position = point_for_position.previous_valid;
8078
8079 self.select(
8080 SelectPhase::BeginColumnar {
8081 position,
8082 reset: false,
8083 mode,
8084 goal_column: point_for_position.exact_unclipped.column(),
8085 },
8086 window,
8087 cx,
8088 );
8089 }
8090
8091 fn update_edit_prediction_preview(
8092 &mut self,
8093 modifiers: &Modifiers,
8094 window: &mut Window,
8095 cx: &mut Context<Self>,
8096 ) {
8097 let mut modifiers_held = false;
8098
8099 // Check bindings for all granularities.
8100 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8101 let granularities = [
8102 EditPredictionGranularity::Full,
8103 EditPredictionGranularity::Line,
8104 EditPredictionGranularity::Word,
8105 ];
8106
8107 for granularity in granularities {
8108 if let Some(keystroke) = self
8109 .accept_edit_prediction_keybind(granularity, window, cx)
8110 .keystroke()
8111 {
8112 modifiers_held = modifiers_held
8113 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8114 }
8115 }
8116
8117 if modifiers_held {
8118 if matches!(
8119 self.edit_prediction_preview,
8120 EditPredictionPreview::Inactive { .. }
8121 ) {
8122 self.edit_prediction_preview = EditPredictionPreview::Active {
8123 previous_scroll_position: None,
8124 since: Instant::now(),
8125 };
8126
8127 self.update_visible_edit_prediction(window, cx);
8128 cx.notify();
8129 }
8130 } else if let EditPredictionPreview::Active {
8131 previous_scroll_position,
8132 since,
8133 } = self.edit_prediction_preview
8134 {
8135 if let (Some(previous_scroll_position), Some(position_map)) =
8136 (previous_scroll_position, self.last_position_map.as_ref())
8137 {
8138 self.set_scroll_position(
8139 previous_scroll_position
8140 .scroll_position(&position_map.snapshot.display_snapshot),
8141 window,
8142 cx,
8143 );
8144 }
8145
8146 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8147 released_too_fast: since.elapsed() < Duration::from_millis(200),
8148 };
8149 self.clear_row_highlights::<EditPredictionPreview>();
8150 self.update_visible_edit_prediction(window, cx);
8151 cx.notify();
8152 }
8153 }
8154
8155 fn update_visible_edit_prediction(
8156 &mut self,
8157 _window: &mut Window,
8158 cx: &mut Context<Self>,
8159 ) -> Option<()> {
8160 if DisableAiSettings::get_global(cx).disable_ai {
8161 return None;
8162 }
8163
8164 if self.ime_transaction.is_some() {
8165 self.discard_edit_prediction(false, cx);
8166 return None;
8167 }
8168
8169 let selection = self.selections.newest_anchor();
8170 let cursor = selection.head();
8171 let multibuffer = self.buffer.read(cx).snapshot(cx);
8172 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8173 let excerpt_id = cursor.excerpt_id;
8174
8175 let show_in_menu = self.show_edit_predictions_in_menu();
8176 let completions_menu_has_precedence = !show_in_menu
8177 && (self.context_menu.borrow().is_some()
8178 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8179
8180 if completions_menu_has_precedence
8181 || !offset_selection.is_empty()
8182 || self
8183 .active_edit_prediction
8184 .as_ref()
8185 .is_some_and(|completion| {
8186 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8187 return false;
8188 };
8189 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8190 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8191 !invalidation_range.contains(&offset_selection.head())
8192 })
8193 {
8194 self.discard_edit_prediction(false, cx);
8195 return None;
8196 }
8197
8198 self.take_active_edit_prediction(cx);
8199 let Some(provider) = self.edit_prediction_provider() else {
8200 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8201 return None;
8202 };
8203
8204 let (buffer, cursor_buffer_position) =
8205 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8206
8207 self.edit_prediction_settings =
8208 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8209
8210 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8211
8212 if self.edit_prediction_indent_conflict {
8213 let cursor_point = cursor.to_point(&multibuffer);
8214 let mut suggested_indent = None;
8215 multibuffer.suggested_indents_callback(
8216 cursor_point.row..cursor_point.row + 1,
8217 |_, indent| {
8218 suggested_indent = Some(indent);
8219 ControlFlow::Break(())
8220 },
8221 cx,
8222 );
8223
8224 if let Some(indent) = suggested_indent
8225 && indent.len == cursor_point.column
8226 {
8227 self.edit_prediction_indent_conflict = false;
8228 }
8229 }
8230
8231 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8232
8233 let (completion_id, edits, edit_preview) = match edit_prediction {
8234 edit_prediction_types::EditPrediction::Local {
8235 id,
8236 edits,
8237 edit_preview,
8238 } => (id, edits, edit_preview),
8239 edit_prediction_types::EditPrediction::Jump {
8240 id,
8241 snapshot,
8242 target,
8243 } => {
8244 if let Some(provider) = &self.edit_prediction_provider {
8245 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8246 }
8247 self.stale_edit_prediction_in_menu = None;
8248 self.active_edit_prediction = Some(EditPredictionState {
8249 inlay_ids: vec![],
8250 completion: EditPrediction::MoveOutside { snapshot, target },
8251 completion_id: id,
8252 invalidation_range: None,
8253 });
8254 cx.notify();
8255 return Some(());
8256 }
8257 };
8258
8259 let edits = edits
8260 .into_iter()
8261 .flat_map(|(range, new_text)| {
8262 Some((
8263 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8264 new_text,
8265 ))
8266 })
8267 .collect::<Vec<_>>();
8268 if edits.is_empty() {
8269 return None;
8270 }
8271
8272 let first_edit_start = edits.first().unwrap().0.start;
8273 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8274 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8275
8276 let last_edit_end = edits.last().unwrap().0.end;
8277 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8278 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8279
8280 let cursor_row = cursor.to_point(&multibuffer).row;
8281
8282 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8283
8284 let mut inlay_ids = Vec::new();
8285 let invalidation_row_range;
8286 let move_invalidation_row_range = if cursor_row < edit_start_row {
8287 Some(cursor_row..edit_end_row)
8288 } else if cursor_row > edit_end_row {
8289 Some(edit_start_row..cursor_row)
8290 } else {
8291 None
8292 };
8293 let supports_jump = self
8294 .edit_prediction_provider
8295 .as_ref()
8296 .map(|provider| provider.provider.supports_jump_to_edit())
8297 .unwrap_or(true);
8298
8299 let is_move = supports_jump
8300 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8301 let completion = if is_move {
8302 if let Some(provider) = &self.edit_prediction_provider {
8303 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8304 }
8305 invalidation_row_range =
8306 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8307 let target = first_edit_start;
8308 EditPrediction::MoveWithin { target, snapshot }
8309 } else {
8310 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8311 && !self.edit_predictions_hidden_for_vim_mode;
8312
8313 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8314 if provider.show_tab_accept_marker() {
8315 EditDisplayMode::TabAccept
8316 } else {
8317 EditDisplayMode::Inline
8318 }
8319 } else {
8320 EditDisplayMode::DiffPopover
8321 };
8322
8323 if show_completions_in_buffer {
8324 if let Some(provider) = &self.edit_prediction_provider {
8325 let suggestion_display_type = match display_mode {
8326 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8327 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8328 SuggestionDisplayType::GhostText
8329 }
8330 };
8331 provider.provider.did_show(suggestion_display_type, cx);
8332 }
8333 if edits
8334 .iter()
8335 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8336 {
8337 let mut inlays = Vec::new();
8338 for (range, new_text) in &edits {
8339 let inlay = Inlay::edit_prediction(
8340 post_inc(&mut self.next_inlay_id),
8341 range.start,
8342 new_text.as_ref(),
8343 );
8344 inlay_ids.push(inlay.id);
8345 inlays.push(inlay);
8346 }
8347
8348 self.splice_inlays(&[], inlays, cx);
8349 } else {
8350 let background_color = cx.theme().status().deleted_background;
8351 self.highlight_text::<EditPredictionHighlight>(
8352 edits.iter().map(|(range, _)| range.clone()).collect(),
8353 HighlightStyle {
8354 background_color: Some(background_color),
8355 ..Default::default()
8356 },
8357 cx,
8358 );
8359 }
8360 }
8361
8362 invalidation_row_range = edit_start_row..edit_end_row;
8363
8364 EditPrediction::Edit {
8365 edits,
8366 edit_preview,
8367 display_mode,
8368 snapshot,
8369 }
8370 };
8371
8372 let invalidation_range = multibuffer
8373 .anchor_before(Point::new(invalidation_row_range.start, 0))
8374 ..multibuffer.anchor_after(Point::new(
8375 invalidation_row_range.end,
8376 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8377 ));
8378
8379 self.stale_edit_prediction_in_menu = None;
8380 self.active_edit_prediction = Some(EditPredictionState {
8381 inlay_ids,
8382 completion,
8383 completion_id,
8384 invalidation_range: Some(invalidation_range),
8385 });
8386
8387 cx.notify();
8388
8389 Some(())
8390 }
8391
8392 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8393 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8394 }
8395
8396 fn clear_tasks(&mut self) {
8397 self.tasks.clear()
8398 }
8399
8400 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8401 if self.tasks.insert(key, value).is_some() {
8402 // This case should hopefully be rare, but just in case...
8403 log::error!(
8404 "multiple different run targets found on a single line, only the last target will be rendered"
8405 )
8406 }
8407 }
8408
8409 /// Get all display points of breakpoints that will be rendered within editor
8410 ///
8411 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8412 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8413 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8414 fn active_breakpoints(
8415 &self,
8416 range: Range<DisplayRow>,
8417 window: &mut Window,
8418 cx: &mut Context<Self>,
8419 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8420 let mut breakpoint_display_points = HashMap::default();
8421
8422 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8423 return breakpoint_display_points;
8424 };
8425
8426 let snapshot = self.snapshot(window, cx);
8427
8428 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8429 let Some(project) = self.project() else {
8430 return breakpoint_display_points;
8431 };
8432
8433 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8434 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8435
8436 for (buffer_snapshot, range, excerpt_id) in
8437 multi_buffer_snapshot.range_to_buffer_ranges(range)
8438 {
8439 let Some(buffer) = project
8440 .read(cx)
8441 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8442 else {
8443 continue;
8444 };
8445 let breakpoints = breakpoint_store.read(cx).breakpoints(
8446 &buffer,
8447 Some(
8448 buffer_snapshot.anchor_before(range.start)
8449 ..buffer_snapshot.anchor_after(range.end),
8450 ),
8451 buffer_snapshot,
8452 cx,
8453 );
8454 for (breakpoint, state) in breakpoints {
8455 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8456 let position = multi_buffer_anchor
8457 .to_point(&multi_buffer_snapshot)
8458 .to_display_point(&snapshot);
8459
8460 breakpoint_display_points.insert(
8461 position.row(),
8462 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8463 );
8464 }
8465 }
8466
8467 breakpoint_display_points
8468 }
8469
8470 fn breakpoint_context_menu(
8471 &self,
8472 anchor: Anchor,
8473 window: &mut Window,
8474 cx: &mut Context<Self>,
8475 ) -> Entity<ui::ContextMenu> {
8476 let weak_editor = cx.weak_entity();
8477 let focus_handle = self.focus_handle(cx);
8478
8479 let row = self
8480 .buffer
8481 .read(cx)
8482 .snapshot(cx)
8483 .summary_for_anchor::<Point>(&anchor)
8484 .row;
8485
8486 let breakpoint = self
8487 .breakpoint_at_row(row, window, cx)
8488 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8489
8490 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8491 "Edit Log Breakpoint"
8492 } else {
8493 "Set Log Breakpoint"
8494 };
8495
8496 let condition_breakpoint_msg = if breakpoint
8497 .as_ref()
8498 .is_some_and(|bp| bp.1.condition.is_some())
8499 {
8500 "Edit Condition Breakpoint"
8501 } else {
8502 "Set Condition Breakpoint"
8503 };
8504
8505 let hit_condition_breakpoint_msg = if breakpoint
8506 .as_ref()
8507 .is_some_and(|bp| bp.1.hit_condition.is_some())
8508 {
8509 "Edit Hit Condition Breakpoint"
8510 } else {
8511 "Set Hit Condition Breakpoint"
8512 };
8513
8514 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8515 "Unset Breakpoint"
8516 } else {
8517 "Set Breakpoint"
8518 };
8519
8520 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8521
8522 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8523 BreakpointState::Enabled => Some("Disable"),
8524 BreakpointState::Disabled => Some("Enable"),
8525 });
8526
8527 let (anchor, breakpoint) =
8528 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8529
8530 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8531 menu.on_blur_subscription(Subscription::new(|| {}))
8532 .context(focus_handle)
8533 .when(run_to_cursor, |this| {
8534 let weak_editor = weak_editor.clone();
8535 this.entry("Run to cursor", None, move |window, cx| {
8536 weak_editor
8537 .update(cx, |editor, cx| {
8538 editor.change_selections(
8539 SelectionEffects::no_scroll(),
8540 window,
8541 cx,
8542 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8543 );
8544 })
8545 .ok();
8546
8547 window.dispatch_action(Box::new(RunToCursor), cx);
8548 })
8549 .separator()
8550 })
8551 .when_some(toggle_state_msg, |this, msg| {
8552 this.entry(msg, None, {
8553 let weak_editor = weak_editor.clone();
8554 let breakpoint = breakpoint.clone();
8555 move |_window, cx| {
8556 weak_editor
8557 .update(cx, |this, cx| {
8558 this.edit_breakpoint_at_anchor(
8559 anchor,
8560 breakpoint.as_ref().clone(),
8561 BreakpointEditAction::InvertState,
8562 cx,
8563 );
8564 })
8565 .log_err();
8566 }
8567 })
8568 })
8569 .entry(set_breakpoint_msg, None, {
8570 let weak_editor = weak_editor.clone();
8571 let breakpoint = breakpoint.clone();
8572 move |_window, cx| {
8573 weak_editor
8574 .update(cx, |this, cx| {
8575 this.edit_breakpoint_at_anchor(
8576 anchor,
8577 breakpoint.as_ref().clone(),
8578 BreakpointEditAction::Toggle,
8579 cx,
8580 );
8581 })
8582 .log_err();
8583 }
8584 })
8585 .entry(log_breakpoint_msg, None, {
8586 let breakpoint = breakpoint.clone();
8587 let weak_editor = weak_editor.clone();
8588 move |window, cx| {
8589 weak_editor
8590 .update(cx, |this, cx| {
8591 this.add_edit_breakpoint_block(
8592 anchor,
8593 breakpoint.as_ref(),
8594 BreakpointPromptEditAction::Log,
8595 window,
8596 cx,
8597 );
8598 })
8599 .log_err();
8600 }
8601 })
8602 .entry(condition_breakpoint_msg, None, {
8603 let breakpoint = breakpoint.clone();
8604 let weak_editor = weak_editor.clone();
8605 move |window, cx| {
8606 weak_editor
8607 .update(cx, |this, cx| {
8608 this.add_edit_breakpoint_block(
8609 anchor,
8610 breakpoint.as_ref(),
8611 BreakpointPromptEditAction::Condition,
8612 window,
8613 cx,
8614 );
8615 })
8616 .log_err();
8617 }
8618 })
8619 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8620 weak_editor
8621 .update(cx, |this, cx| {
8622 this.add_edit_breakpoint_block(
8623 anchor,
8624 breakpoint.as_ref(),
8625 BreakpointPromptEditAction::HitCondition,
8626 window,
8627 cx,
8628 );
8629 })
8630 .log_err();
8631 })
8632 })
8633 }
8634
8635 fn render_breakpoint(
8636 &self,
8637 position: Anchor,
8638 row: DisplayRow,
8639 breakpoint: &Breakpoint,
8640 state: Option<BreakpointSessionState>,
8641 cx: &mut Context<Self>,
8642 ) -> IconButton {
8643 let is_rejected = state.is_some_and(|s| !s.verified);
8644 // Is it a breakpoint that shows up when hovering over gutter?
8645 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8646 (false, false),
8647 |PhantomBreakpointIndicator {
8648 is_active,
8649 display_row,
8650 collides_with_existing_breakpoint,
8651 }| {
8652 (
8653 is_active && display_row == row,
8654 collides_with_existing_breakpoint,
8655 )
8656 },
8657 );
8658
8659 let (color, icon) = {
8660 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8661 (false, false) => ui::IconName::DebugBreakpoint,
8662 (true, false) => ui::IconName::DebugLogBreakpoint,
8663 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8664 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8665 };
8666
8667 let theme_colors = cx.theme().colors();
8668
8669 let color = if is_phantom {
8670 if collides_with_existing {
8671 Color::Custom(
8672 theme_colors
8673 .debugger_accent
8674 .blend(theme_colors.text.opacity(0.6)),
8675 )
8676 } else {
8677 Color::Hint
8678 }
8679 } else if is_rejected {
8680 Color::Disabled
8681 } else {
8682 Color::Debugger
8683 };
8684
8685 (color, icon)
8686 };
8687
8688 let breakpoint = Arc::from(breakpoint.clone());
8689
8690 let alt_as_text = gpui::Keystroke {
8691 modifiers: Modifiers::secondary_key(),
8692 ..Default::default()
8693 };
8694 let primary_action_text = if breakpoint.is_disabled() {
8695 "Enable breakpoint"
8696 } else if is_phantom && !collides_with_existing {
8697 "Set breakpoint"
8698 } else {
8699 "Unset breakpoint"
8700 };
8701 let focus_handle = self.focus_handle.clone();
8702
8703 let meta = if is_rejected {
8704 SharedString::from("No executable code is associated with this line.")
8705 } else if collides_with_existing && !breakpoint.is_disabled() {
8706 SharedString::from(format!(
8707 "{alt_as_text}-click to disable,\nright-click for more options."
8708 ))
8709 } else {
8710 SharedString::from("Right-click for more options.")
8711 };
8712 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8713 .icon_size(IconSize::XSmall)
8714 .size(ui::ButtonSize::None)
8715 .when(is_rejected, |this| {
8716 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8717 })
8718 .icon_color(color)
8719 .style(ButtonStyle::Transparent)
8720 .on_click(cx.listener({
8721 move |editor, event: &ClickEvent, window, cx| {
8722 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8723 BreakpointEditAction::InvertState
8724 } else {
8725 BreakpointEditAction::Toggle
8726 };
8727
8728 window.focus(&editor.focus_handle(cx), cx);
8729 editor.edit_breakpoint_at_anchor(
8730 position,
8731 breakpoint.as_ref().clone(),
8732 edit_action,
8733 cx,
8734 );
8735 }
8736 }))
8737 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8738 editor.set_breakpoint_context_menu(
8739 row,
8740 Some(position),
8741 event.position(),
8742 window,
8743 cx,
8744 );
8745 }))
8746 .tooltip(move |_window, cx| {
8747 Tooltip::with_meta_in(
8748 primary_action_text,
8749 Some(&ToggleBreakpoint),
8750 meta.clone(),
8751 &focus_handle,
8752 cx,
8753 )
8754 })
8755 }
8756
8757 fn build_tasks_context(
8758 project: &Entity<Project>,
8759 buffer: &Entity<Buffer>,
8760 buffer_row: u32,
8761 tasks: &Arc<RunnableTasks>,
8762 cx: &mut Context<Self>,
8763 ) -> Task<Option<task::TaskContext>> {
8764 let position = Point::new(buffer_row, tasks.column);
8765 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8766 let location = Location {
8767 buffer: buffer.clone(),
8768 range: range_start..range_start,
8769 };
8770 // Fill in the environmental variables from the tree-sitter captures
8771 let mut captured_task_variables = TaskVariables::default();
8772 for (capture_name, value) in tasks.extra_variables.clone() {
8773 captured_task_variables.insert(
8774 task::VariableName::Custom(capture_name.into()),
8775 value.clone(),
8776 );
8777 }
8778 project.update(cx, |project, cx| {
8779 project.task_store().update(cx, |task_store, cx| {
8780 task_store.task_context_for_location(captured_task_variables, location, cx)
8781 })
8782 })
8783 }
8784
8785 pub fn spawn_nearest_task(
8786 &mut self,
8787 action: &SpawnNearestTask,
8788 window: &mut Window,
8789 cx: &mut Context<Self>,
8790 ) {
8791 let Some((workspace, _)) = self.workspace.clone() else {
8792 return;
8793 };
8794 let Some(project) = self.project.clone() else {
8795 return;
8796 };
8797
8798 // Try to find a closest, enclosing node using tree-sitter that has a task
8799 let Some((buffer, buffer_row, tasks)) = self
8800 .find_enclosing_node_task(cx)
8801 // Or find the task that's closest in row-distance.
8802 .or_else(|| self.find_closest_task(cx))
8803 else {
8804 return;
8805 };
8806
8807 let reveal_strategy = action.reveal;
8808 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8809 cx.spawn_in(window, async move |_, cx| {
8810 let context = task_context.await?;
8811 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8812
8813 let resolved = &mut resolved_task.resolved;
8814 resolved.reveal = reveal_strategy;
8815
8816 workspace
8817 .update_in(cx, |workspace, window, cx| {
8818 workspace.schedule_resolved_task(
8819 task_source_kind,
8820 resolved_task,
8821 false,
8822 window,
8823 cx,
8824 );
8825 })
8826 .ok()
8827 })
8828 .detach();
8829 }
8830
8831 fn find_closest_task(
8832 &mut self,
8833 cx: &mut Context<Self>,
8834 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8835 let cursor_row = self
8836 .selections
8837 .newest_adjusted(&self.display_snapshot(cx))
8838 .head()
8839 .row;
8840
8841 let ((buffer_id, row), tasks) = self
8842 .tasks
8843 .iter()
8844 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8845
8846 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8847 let tasks = Arc::new(tasks.to_owned());
8848 Some((buffer, *row, tasks))
8849 }
8850
8851 fn find_enclosing_node_task(
8852 &mut self,
8853 cx: &mut Context<Self>,
8854 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8855 let snapshot = self.buffer.read(cx).snapshot(cx);
8856 let offset = self
8857 .selections
8858 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8859 .head();
8860 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8861 let offset = excerpt.map_offset_to_buffer(offset);
8862 let buffer_id = excerpt.buffer().remote_id();
8863
8864 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8865 let mut cursor = layer.node().walk();
8866
8867 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8868 if cursor.node().end_byte() == offset.0 {
8869 cursor.goto_next_sibling();
8870 }
8871 }
8872
8873 // Ascend to the smallest ancestor that contains the range and has a task.
8874 loop {
8875 let node = cursor.node();
8876 let node_range = node.byte_range();
8877 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8878
8879 // Check if this node contains our offset
8880 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8881 // If it contains offset, check for task
8882 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8883 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8884 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8885 }
8886 }
8887
8888 if !cursor.goto_parent() {
8889 break;
8890 }
8891 }
8892 None
8893 }
8894
8895 fn render_run_indicator(
8896 &self,
8897 _style: &EditorStyle,
8898 is_active: bool,
8899 row: DisplayRow,
8900 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8901 cx: &mut Context<Self>,
8902 ) -> IconButton {
8903 let color = Color::Muted;
8904 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8905
8906 IconButton::new(
8907 ("run_indicator", row.0 as usize),
8908 ui::IconName::PlayOutlined,
8909 )
8910 .shape(ui::IconButtonShape::Square)
8911 .icon_size(IconSize::XSmall)
8912 .icon_color(color)
8913 .toggle_state(is_active)
8914 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8915 let quick_launch = match e {
8916 ClickEvent::Keyboard(_) => true,
8917 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8918 };
8919
8920 window.focus(&editor.focus_handle(cx), cx);
8921 editor.toggle_code_actions(
8922 &ToggleCodeActions {
8923 deployed_from: Some(CodeActionSource::RunMenu(row)),
8924 quick_launch,
8925 },
8926 window,
8927 cx,
8928 );
8929 }))
8930 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8931 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8932 }))
8933 }
8934
8935 pub fn context_menu_visible(&self) -> bool {
8936 !self.edit_prediction_preview_is_active()
8937 && self
8938 .context_menu
8939 .borrow()
8940 .as_ref()
8941 .is_some_and(|menu| menu.visible())
8942 }
8943
8944 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8945 self.context_menu
8946 .borrow()
8947 .as_ref()
8948 .map(|menu| menu.origin())
8949 }
8950
8951 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8952 self.context_menu_options = Some(options);
8953 }
8954
8955 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8956 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8957
8958 fn render_edit_prediction_popover(
8959 &mut self,
8960 text_bounds: &Bounds<Pixels>,
8961 content_origin: gpui::Point<Pixels>,
8962 right_margin: Pixels,
8963 editor_snapshot: &EditorSnapshot,
8964 visible_row_range: Range<DisplayRow>,
8965 scroll_top: ScrollOffset,
8966 scroll_bottom: ScrollOffset,
8967 line_layouts: &[LineWithInvisibles],
8968 line_height: Pixels,
8969 scroll_position: gpui::Point<ScrollOffset>,
8970 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8971 newest_selection_head: Option<DisplayPoint>,
8972 editor_width: Pixels,
8973 style: &EditorStyle,
8974 window: &mut Window,
8975 cx: &mut App,
8976 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8977 if self.mode().is_minimap() {
8978 return None;
8979 }
8980 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8981
8982 if self.edit_prediction_visible_in_cursor_popover(true) {
8983 return None;
8984 }
8985
8986 match &active_edit_prediction.completion {
8987 EditPrediction::MoveWithin { target, .. } => {
8988 let target_display_point = target.to_display_point(editor_snapshot);
8989
8990 if self.edit_prediction_requires_modifier() {
8991 if !self.edit_prediction_preview_is_active() {
8992 return None;
8993 }
8994
8995 self.render_edit_prediction_modifier_jump_popover(
8996 text_bounds,
8997 content_origin,
8998 visible_row_range,
8999 line_layouts,
9000 line_height,
9001 scroll_pixel_position,
9002 newest_selection_head,
9003 target_display_point,
9004 window,
9005 cx,
9006 )
9007 } else {
9008 self.render_edit_prediction_eager_jump_popover(
9009 text_bounds,
9010 content_origin,
9011 editor_snapshot,
9012 visible_row_range,
9013 scroll_top,
9014 scroll_bottom,
9015 line_height,
9016 scroll_pixel_position,
9017 target_display_point,
9018 editor_width,
9019 window,
9020 cx,
9021 )
9022 }
9023 }
9024 EditPrediction::Edit {
9025 display_mode: EditDisplayMode::Inline,
9026 ..
9027 } => None,
9028 EditPrediction::Edit {
9029 display_mode: EditDisplayMode::TabAccept,
9030 edits,
9031 ..
9032 } => {
9033 let range = &edits.first()?.0;
9034 let target_display_point = range.end.to_display_point(editor_snapshot);
9035
9036 self.render_edit_prediction_end_of_line_popover(
9037 "Accept",
9038 editor_snapshot,
9039 visible_row_range,
9040 target_display_point,
9041 line_height,
9042 scroll_pixel_position,
9043 content_origin,
9044 editor_width,
9045 window,
9046 cx,
9047 )
9048 }
9049 EditPrediction::Edit {
9050 edits,
9051 edit_preview,
9052 display_mode: EditDisplayMode::DiffPopover,
9053 snapshot,
9054 } => self.render_edit_prediction_diff_popover(
9055 text_bounds,
9056 content_origin,
9057 right_margin,
9058 editor_snapshot,
9059 visible_row_range,
9060 line_layouts,
9061 line_height,
9062 scroll_position,
9063 scroll_pixel_position,
9064 newest_selection_head,
9065 editor_width,
9066 style,
9067 edits,
9068 edit_preview,
9069 snapshot,
9070 window,
9071 cx,
9072 ),
9073 EditPrediction::MoveOutside { snapshot, .. } => {
9074 let mut element = self
9075 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9076 .into_any();
9077
9078 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9079 let origin_x = text_bounds.size.width - size.width - px(30.);
9080 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9081 element.prepaint_at(origin, window, cx);
9082
9083 Some((element, origin))
9084 }
9085 }
9086 }
9087
9088 fn render_edit_prediction_modifier_jump_popover(
9089 &mut self,
9090 text_bounds: &Bounds<Pixels>,
9091 content_origin: gpui::Point<Pixels>,
9092 visible_row_range: Range<DisplayRow>,
9093 line_layouts: &[LineWithInvisibles],
9094 line_height: Pixels,
9095 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9096 newest_selection_head: Option<DisplayPoint>,
9097 target_display_point: DisplayPoint,
9098 window: &mut Window,
9099 cx: &mut App,
9100 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9101 let scrolled_content_origin =
9102 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9103
9104 const SCROLL_PADDING_Y: Pixels = px(12.);
9105
9106 if target_display_point.row() < visible_row_range.start {
9107 return self.render_edit_prediction_scroll_popover(
9108 |_| SCROLL_PADDING_Y,
9109 IconName::ArrowUp,
9110 visible_row_range,
9111 line_layouts,
9112 newest_selection_head,
9113 scrolled_content_origin,
9114 window,
9115 cx,
9116 );
9117 } else if target_display_point.row() >= visible_row_range.end {
9118 return self.render_edit_prediction_scroll_popover(
9119 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9120 IconName::ArrowDown,
9121 visible_row_range,
9122 line_layouts,
9123 newest_selection_head,
9124 scrolled_content_origin,
9125 window,
9126 cx,
9127 );
9128 }
9129
9130 const POLE_WIDTH: Pixels = px(2.);
9131
9132 let line_layout =
9133 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9134 let target_column = target_display_point.column() as usize;
9135
9136 let target_x = line_layout.x_for_index(target_column);
9137 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9138 - scroll_pixel_position.y;
9139
9140 let flag_on_right = target_x < text_bounds.size.width / 2.;
9141
9142 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9143 border_color.l += 0.001;
9144
9145 let mut element = v_flex()
9146 .items_end()
9147 .when(flag_on_right, |el| el.items_start())
9148 .child(if flag_on_right {
9149 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9150 .rounded_bl(px(0.))
9151 .rounded_tl(px(0.))
9152 .border_l_2()
9153 .border_color(border_color)
9154 } else {
9155 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9156 .rounded_br(px(0.))
9157 .rounded_tr(px(0.))
9158 .border_r_2()
9159 .border_color(border_color)
9160 })
9161 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9162 .into_any();
9163
9164 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9165
9166 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9167 - point(
9168 if flag_on_right {
9169 POLE_WIDTH
9170 } else {
9171 size.width - POLE_WIDTH
9172 },
9173 size.height - line_height,
9174 );
9175
9176 origin.x = origin.x.max(content_origin.x);
9177
9178 element.prepaint_at(origin, window, cx);
9179
9180 Some((element, origin))
9181 }
9182
9183 fn render_edit_prediction_scroll_popover(
9184 &mut self,
9185 to_y: impl Fn(Size<Pixels>) -> Pixels,
9186 scroll_icon: IconName,
9187 visible_row_range: Range<DisplayRow>,
9188 line_layouts: &[LineWithInvisibles],
9189 newest_selection_head: Option<DisplayPoint>,
9190 scrolled_content_origin: gpui::Point<Pixels>,
9191 window: &mut Window,
9192 cx: &mut App,
9193 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9194 let mut element = self
9195 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9196 .into_any();
9197
9198 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9199
9200 let cursor = newest_selection_head?;
9201 let cursor_row_layout =
9202 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9203 let cursor_column = cursor.column() as usize;
9204
9205 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9206
9207 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9208
9209 element.prepaint_at(origin, window, cx);
9210 Some((element, origin))
9211 }
9212
9213 fn render_edit_prediction_eager_jump_popover(
9214 &mut self,
9215 text_bounds: &Bounds<Pixels>,
9216 content_origin: gpui::Point<Pixels>,
9217 editor_snapshot: &EditorSnapshot,
9218 visible_row_range: Range<DisplayRow>,
9219 scroll_top: ScrollOffset,
9220 scroll_bottom: ScrollOffset,
9221 line_height: Pixels,
9222 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9223 target_display_point: DisplayPoint,
9224 editor_width: Pixels,
9225 window: &mut Window,
9226 cx: &mut App,
9227 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9228 if target_display_point.row().as_f64() < scroll_top {
9229 let mut element = self
9230 .render_edit_prediction_line_popover(
9231 "Jump to Edit",
9232 Some(IconName::ArrowUp),
9233 window,
9234 cx,
9235 )
9236 .into_any();
9237
9238 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9239 let offset = point(
9240 (text_bounds.size.width - size.width) / 2.,
9241 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9242 );
9243
9244 let origin = text_bounds.origin + offset;
9245 element.prepaint_at(origin, window, cx);
9246 Some((element, origin))
9247 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9248 let mut element = self
9249 .render_edit_prediction_line_popover(
9250 "Jump to Edit",
9251 Some(IconName::ArrowDown),
9252 window,
9253 cx,
9254 )
9255 .into_any();
9256
9257 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9258 let offset = point(
9259 (text_bounds.size.width - size.width) / 2.,
9260 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9261 );
9262
9263 let origin = text_bounds.origin + offset;
9264 element.prepaint_at(origin, window, cx);
9265 Some((element, origin))
9266 } else {
9267 self.render_edit_prediction_end_of_line_popover(
9268 "Jump to Edit",
9269 editor_snapshot,
9270 visible_row_range,
9271 target_display_point,
9272 line_height,
9273 scroll_pixel_position,
9274 content_origin,
9275 editor_width,
9276 window,
9277 cx,
9278 )
9279 }
9280 }
9281
9282 fn render_edit_prediction_end_of_line_popover(
9283 self: &mut Editor,
9284 label: &'static str,
9285 editor_snapshot: &EditorSnapshot,
9286 visible_row_range: Range<DisplayRow>,
9287 target_display_point: DisplayPoint,
9288 line_height: Pixels,
9289 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9290 content_origin: gpui::Point<Pixels>,
9291 editor_width: Pixels,
9292 window: &mut Window,
9293 cx: &mut App,
9294 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9295 let target_line_end = DisplayPoint::new(
9296 target_display_point.row(),
9297 editor_snapshot.line_len(target_display_point.row()),
9298 );
9299
9300 let mut element = self
9301 .render_edit_prediction_line_popover(label, None, window, cx)
9302 .into_any();
9303
9304 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9305
9306 let line_origin =
9307 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9308
9309 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9310 let mut origin = start_point
9311 + line_origin
9312 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9313 origin.x = origin.x.max(content_origin.x);
9314
9315 let max_x = content_origin.x + editor_width - size.width;
9316
9317 if origin.x > max_x {
9318 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9319
9320 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9321 origin.y += offset;
9322 IconName::ArrowUp
9323 } else {
9324 origin.y -= offset;
9325 IconName::ArrowDown
9326 };
9327
9328 element = self
9329 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9330 .into_any();
9331
9332 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9333
9334 origin.x = content_origin.x + editor_width - size.width - px(2.);
9335 }
9336
9337 element.prepaint_at(origin, window, cx);
9338 Some((element, origin))
9339 }
9340
9341 fn render_edit_prediction_diff_popover(
9342 self: &Editor,
9343 text_bounds: &Bounds<Pixels>,
9344 content_origin: gpui::Point<Pixels>,
9345 right_margin: Pixels,
9346 editor_snapshot: &EditorSnapshot,
9347 visible_row_range: Range<DisplayRow>,
9348 line_layouts: &[LineWithInvisibles],
9349 line_height: Pixels,
9350 scroll_position: gpui::Point<ScrollOffset>,
9351 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9352 newest_selection_head: Option<DisplayPoint>,
9353 editor_width: Pixels,
9354 style: &EditorStyle,
9355 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9356 edit_preview: &Option<language::EditPreview>,
9357 snapshot: &language::BufferSnapshot,
9358 window: &mut Window,
9359 cx: &mut App,
9360 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9361 let edit_start = edits
9362 .first()
9363 .unwrap()
9364 .0
9365 .start
9366 .to_display_point(editor_snapshot);
9367 let edit_end = edits
9368 .last()
9369 .unwrap()
9370 .0
9371 .end
9372 .to_display_point(editor_snapshot);
9373
9374 let is_visible = visible_row_range.contains(&edit_start.row())
9375 || visible_row_range.contains(&edit_end.row());
9376 if !is_visible {
9377 return None;
9378 }
9379
9380 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9381 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9382 } else {
9383 // Fallback for providers without edit_preview
9384 crate::edit_prediction_fallback_text(edits, cx)
9385 };
9386
9387 let styled_text = highlighted_edits.to_styled_text(&style.text);
9388 let line_count = highlighted_edits.text.lines().count();
9389
9390 const BORDER_WIDTH: Pixels = px(1.);
9391
9392 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9393 let has_keybind = keybind.is_some();
9394
9395 let mut element = h_flex()
9396 .items_start()
9397 .child(
9398 h_flex()
9399 .bg(cx.theme().colors().editor_background)
9400 .border(BORDER_WIDTH)
9401 .shadow_xs()
9402 .border_color(cx.theme().colors().border)
9403 .rounded_l_lg()
9404 .when(line_count > 1, |el| el.rounded_br_lg())
9405 .pr_1()
9406 .child(styled_text),
9407 )
9408 .child(
9409 h_flex()
9410 .h(line_height + BORDER_WIDTH * 2.)
9411 .px_1p5()
9412 .gap_1()
9413 // Workaround: For some reason, there's a gap if we don't do this
9414 .ml(-BORDER_WIDTH)
9415 .shadow(vec![gpui::BoxShadow {
9416 color: gpui::black().opacity(0.05),
9417 offset: point(px(1.), px(1.)),
9418 blur_radius: px(2.),
9419 spread_radius: px(0.),
9420 }])
9421 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9422 .border(BORDER_WIDTH)
9423 .border_color(cx.theme().colors().border)
9424 .rounded_r_lg()
9425 .id("edit_prediction_diff_popover_keybind")
9426 .when(!has_keybind, |el| {
9427 let status_colors = cx.theme().status();
9428
9429 el.bg(status_colors.error_background)
9430 .border_color(status_colors.error.opacity(0.6))
9431 .child(Icon::new(IconName::Info).color(Color::Error))
9432 .cursor_default()
9433 .hoverable_tooltip(move |_window, cx| {
9434 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9435 })
9436 })
9437 .children(keybind),
9438 )
9439 .into_any();
9440
9441 let longest_row =
9442 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9443 let longest_line_width = if visible_row_range.contains(&longest_row) {
9444 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9445 } else {
9446 layout_line(
9447 longest_row,
9448 editor_snapshot,
9449 style,
9450 editor_width,
9451 |_| false,
9452 window,
9453 cx,
9454 )
9455 .width
9456 };
9457
9458 let viewport_bounds =
9459 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9460 right: -right_margin,
9461 ..Default::default()
9462 });
9463
9464 let x_after_longest = Pixels::from(
9465 ScrollPixelOffset::from(
9466 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9467 ) - scroll_pixel_position.x,
9468 );
9469
9470 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9471
9472 // Fully visible if it can be displayed within the window (allow overlapping other
9473 // panes). However, this is only allowed if the popover starts within text_bounds.
9474 let can_position_to_the_right = x_after_longest < text_bounds.right()
9475 && x_after_longest + element_bounds.width < viewport_bounds.right();
9476
9477 let mut origin = if can_position_to_the_right {
9478 point(
9479 x_after_longest,
9480 text_bounds.origin.y
9481 + Pixels::from(
9482 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9483 - scroll_pixel_position.y,
9484 ),
9485 )
9486 } else {
9487 let cursor_row = newest_selection_head.map(|head| head.row());
9488 let above_edit = edit_start
9489 .row()
9490 .0
9491 .checked_sub(line_count as u32)
9492 .map(DisplayRow);
9493 let below_edit = Some(edit_end.row() + 1);
9494 let above_cursor =
9495 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9496 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9497
9498 // Place the edit popover adjacent to the edit if there is a location
9499 // available that is onscreen and does not obscure the cursor. Otherwise,
9500 // place it adjacent to the cursor.
9501 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9502 .into_iter()
9503 .flatten()
9504 .find(|&start_row| {
9505 let end_row = start_row + line_count as u32;
9506 visible_row_range.contains(&start_row)
9507 && visible_row_range.contains(&end_row)
9508 && cursor_row
9509 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9510 })?;
9511
9512 content_origin
9513 + point(
9514 Pixels::from(-scroll_pixel_position.x),
9515 Pixels::from(
9516 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9517 ),
9518 )
9519 };
9520
9521 origin.x -= BORDER_WIDTH;
9522
9523 window.defer_draw(element, origin, 1);
9524
9525 // Do not return an element, since it will already be drawn due to defer_draw.
9526 None
9527 }
9528
9529 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9530 px(30.)
9531 }
9532
9533 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9534 if self.read_only(cx) {
9535 cx.theme().players().read_only()
9536 } else {
9537 self.style.as_ref().unwrap().local_player
9538 }
9539 }
9540
9541 fn render_edit_prediction_accept_keybind(
9542 &self,
9543 window: &mut Window,
9544 cx: &mut App,
9545 ) -> Option<AnyElement> {
9546 let accept_binding =
9547 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9548 let accept_keystroke = accept_binding.keystroke()?;
9549
9550 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9551
9552 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9553 Color::Accent
9554 } else {
9555 Color::Muted
9556 };
9557
9558 h_flex()
9559 .px_0p5()
9560 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9561 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9562 .text_size(TextSize::XSmall.rems(cx))
9563 .child(h_flex().children(ui::render_modifiers(
9564 accept_keystroke.modifiers(),
9565 PlatformStyle::platform(),
9566 Some(modifiers_color),
9567 Some(IconSize::XSmall.rems().into()),
9568 true,
9569 )))
9570 .when(is_platform_style_mac, |parent| {
9571 parent.child(accept_keystroke.key().to_string())
9572 })
9573 .when(!is_platform_style_mac, |parent| {
9574 parent.child(
9575 Key::new(
9576 util::capitalize(accept_keystroke.key()),
9577 Some(Color::Default),
9578 )
9579 .size(Some(IconSize::XSmall.rems().into())),
9580 )
9581 })
9582 .into_any()
9583 .into()
9584 }
9585
9586 fn render_edit_prediction_line_popover(
9587 &self,
9588 label: impl Into<SharedString>,
9589 icon: Option<IconName>,
9590 window: &mut Window,
9591 cx: &mut App,
9592 ) -> Stateful<Div> {
9593 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9594
9595 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9596 let has_keybind = keybind.is_some();
9597
9598 h_flex()
9599 .id("ep-line-popover")
9600 .py_0p5()
9601 .pl_1()
9602 .pr(padding_right)
9603 .gap_1()
9604 .rounded_md()
9605 .border_1()
9606 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9607 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9608 .shadow_xs()
9609 .when(!has_keybind, |el| {
9610 let status_colors = cx.theme().status();
9611
9612 el.bg(status_colors.error_background)
9613 .border_color(status_colors.error.opacity(0.6))
9614 .pl_2()
9615 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9616 .cursor_default()
9617 .hoverable_tooltip(move |_window, cx| {
9618 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9619 })
9620 })
9621 .children(keybind)
9622 .child(
9623 Label::new(label)
9624 .size(LabelSize::Small)
9625 .when(!has_keybind, |el| {
9626 el.color(cx.theme().status().error.into()).strikethrough()
9627 }),
9628 )
9629 .when(!has_keybind, |el| {
9630 el.child(
9631 h_flex().ml_1().child(
9632 Icon::new(IconName::Info)
9633 .size(IconSize::Small)
9634 .color(cx.theme().status().error.into()),
9635 ),
9636 )
9637 })
9638 .when_some(icon, |element, icon| {
9639 element.child(
9640 div()
9641 .mt(px(1.5))
9642 .child(Icon::new(icon).size(IconSize::Small)),
9643 )
9644 })
9645 }
9646
9647 fn render_edit_prediction_jump_outside_popover(
9648 &self,
9649 snapshot: &BufferSnapshot,
9650 window: &mut Window,
9651 cx: &mut App,
9652 ) -> Stateful<Div> {
9653 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9654 let has_keybind = keybind.is_some();
9655
9656 let file_name = snapshot
9657 .file()
9658 .map(|file| SharedString::new(file.file_name(cx)))
9659 .unwrap_or(SharedString::new_static("untitled"));
9660
9661 h_flex()
9662 .id("ep-jump-outside-popover")
9663 .py_1()
9664 .px_2()
9665 .gap_1()
9666 .rounded_md()
9667 .border_1()
9668 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9669 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9670 .shadow_xs()
9671 .when(!has_keybind, |el| {
9672 let status_colors = cx.theme().status();
9673
9674 el.bg(status_colors.error_background)
9675 .border_color(status_colors.error.opacity(0.6))
9676 .pl_2()
9677 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9678 .cursor_default()
9679 .hoverable_tooltip(move |_window, cx| {
9680 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9681 })
9682 })
9683 .children(keybind)
9684 .child(
9685 Label::new(file_name)
9686 .size(LabelSize::Small)
9687 .buffer_font(cx)
9688 .when(!has_keybind, |el| {
9689 el.color(cx.theme().status().error.into()).strikethrough()
9690 }),
9691 )
9692 .when(!has_keybind, |el| {
9693 el.child(
9694 h_flex().ml_1().child(
9695 Icon::new(IconName::Info)
9696 .size(IconSize::Small)
9697 .color(cx.theme().status().error.into()),
9698 ),
9699 )
9700 })
9701 .child(
9702 div()
9703 .mt(px(1.5))
9704 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9705 )
9706 }
9707
9708 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9709 let accent_color = cx.theme().colors().text_accent;
9710 let editor_bg_color = cx.theme().colors().editor_background;
9711 editor_bg_color.blend(accent_color.opacity(0.1))
9712 }
9713
9714 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9715 let accent_color = cx.theme().colors().text_accent;
9716 let editor_bg_color = cx.theme().colors().editor_background;
9717 editor_bg_color.blend(accent_color.opacity(0.6))
9718 }
9719 fn get_prediction_provider_icon_name(
9720 provider: &Option<RegisteredEditPredictionDelegate>,
9721 ) -> IconName {
9722 match provider {
9723 Some(provider) => match provider.provider.name() {
9724 "copilot" => IconName::Copilot,
9725 "supermaven" => IconName::Supermaven,
9726 _ => IconName::ZedPredict,
9727 },
9728 None => IconName::ZedPredict,
9729 }
9730 }
9731
9732 fn render_edit_prediction_cursor_popover(
9733 &self,
9734 min_width: Pixels,
9735 max_width: Pixels,
9736 cursor_point: Point,
9737 style: &EditorStyle,
9738 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9739 _window: &Window,
9740 cx: &mut Context<Editor>,
9741 ) -> Option<AnyElement> {
9742 let provider = self.edit_prediction_provider.as_ref()?;
9743 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9744
9745 let is_refreshing = provider.provider.is_refreshing(cx);
9746
9747 fn pending_completion_container(icon: IconName) -> Div {
9748 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9749 }
9750
9751 let completion = match &self.active_edit_prediction {
9752 Some(prediction) => {
9753 if !self.has_visible_completions_menu() {
9754 const RADIUS: Pixels = px(6.);
9755 const BORDER_WIDTH: Pixels = px(1.);
9756
9757 return Some(
9758 h_flex()
9759 .elevation_2(cx)
9760 .border(BORDER_WIDTH)
9761 .border_color(cx.theme().colors().border)
9762 .when(accept_keystroke.is_none(), |el| {
9763 el.border_color(cx.theme().status().error)
9764 })
9765 .rounded(RADIUS)
9766 .rounded_tl(px(0.))
9767 .overflow_hidden()
9768 .child(div().px_1p5().child(match &prediction.completion {
9769 EditPrediction::MoveWithin { target, snapshot } => {
9770 use text::ToPoint as _;
9771 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9772 {
9773 Icon::new(IconName::ZedPredictDown)
9774 } else {
9775 Icon::new(IconName::ZedPredictUp)
9776 }
9777 }
9778 EditPrediction::MoveOutside { .. } => {
9779 // TODO [zeta2] custom icon for external jump?
9780 Icon::new(provider_icon)
9781 }
9782 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9783 }))
9784 .child(
9785 h_flex()
9786 .gap_1()
9787 .py_1()
9788 .px_2()
9789 .rounded_r(RADIUS - BORDER_WIDTH)
9790 .border_l_1()
9791 .border_color(cx.theme().colors().border)
9792 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9793 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9794 el.child(
9795 Label::new("Hold")
9796 .size(LabelSize::Small)
9797 .when(accept_keystroke.is_none(), |el| {
9798 el.strikethrough()
9799 })
9800 .line_height_style(LineHeightStyle::UiLabel),
9801 )
9802 })
9803 .id("edit_prediction_cursor_popover_keybind")
9804 .when(accept_keystroke.is_none(), |el| {
9805 let status_colors = cx.theme().status();
9806
9807 el.bg(status_colors.error_background)
9808 .border_color(status_colors.error.opacity(0.6))
9809 .child(Icon::new(IconName::Info).color(Color::Error))
9810 .cursor_default()
9811 .hoverable_tooltip(move |_window, cx| {
9812 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9813 .into()
9814 })
9815 })
9816 .when_some(
9817 accept_keystroke.as_ref(),
9818 |el, accept_keystroke| {
9819 el.child(h_flex().children(ui::render_modifiers(
9820 accept_keystroke.modifiers(),
9821 PlatformStyle::platform(),
9822 Some(Color::Default),
9823 Some(IconSize::XSmall.rems().into()),
9824 false,
9825 )))
9826 },
9827 ),
9828 )
9829 .into_any(),
9830 );
9831 }
9832
9833 self.render_edit_prediction_cursor_popover_preview(
9834 prediction,
9835 cursor_point,
9836 style,
9837 cx,
9838 )?
9839 }
9840
9841 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9842 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9843 stale_completion,
9844 cursor_point,
9845 style,
9846 cx,
9847 )?,
9848
9849 None => pending_completion_container(provider_icon)
9850 .child(Label::new("...").size(LabelSize::Small)),
9851 },
9852
9853 None => pending_completion_container(provider_icon)
9854 .child(Label::new("...").size(LabelSize::Small)),
9855 };
9856
9857 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9858 completion
9859 .with_animation(
9860 "loading-completion",
9861 Animation::new(Duration::from_secs(2))
9862 .repeat()
9863 .with_easing(pulsating_between(0.4, 0.8)),
9864 |label, delta| label.opacity(delta),
9865 )
9866 .into_any_element()
9867 } else {
9868 completion.into_any_element()
9869 };
9870
9871 let has_completion = self.active_edit_prediction.is_some();
9872
9873 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9874 Some(
9875 h_flex()
9876 .min_w(min_width)
9877 .max_w(max_width)
9878 .flex_1()
9879 .elevation_2(cx)
9880 .border_color(cx.theme().colors().border)
9881 .child(
9882 div()
9883 .flex_1()
9884 .py_1()
9885 .px_2()
9886 .overflow_hidden()
9887 .child(completion),
9888 )
9889 .when_some(accept_keystroke, |el, accept_keystroke| {
9890 if !accept_keystroke.modifiers().modified() {
9891 return el;
9892 }
9893
9894 el.child(
9895 h_flex()
9896 .h_full()
9897 .border_l_1()
9898 .rounded_r_lg()
9899 .border_color(cx.theme().colors().border)
9900 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9901 .gap_1()
9902 .py_1()
9903 .px_2()
9904 .child(
9905 h_flex()
9906 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9907 .when(is_platform_style_mac, |parent| parent.gap_1())
9908 .child(h_flex().children(ui::render_modifiers(
9909 accept_keystroke.modifiers(),
9910 PlatformStyle::platform(),
9911 Some(if !has_completion {
9912 Color::Muted
9913 } else {
9914 Color::Default
9915 }),
9916 None,
9917 false,
9918 ))),
9919 )
9920 .child(Label::new("Preview").into_any_element())
9921 .opacity(if has_completion { 1.0 } else { 0.4 }),
9922 )
9923 })
9924 .into_any(),
9925 )
9926 }
9927
9928 fn render_edit_prediction_cursor_popover_preview(
9929 &self,
9930 completion: &EditPredictionState,
9931 cursor_point: Point,
9932 style: &EditorStyle,
9933 cx: &mut Context<Editor>,
9934 ) -> Option<Div> {
9935 use text::ToPoint as _;
9936
9937 fn render_relative_row_jump(
9938 prefix: impl Into<String>,
9939 current_row: u32,
9940 target_row: u32,
9941 ) -> Div {
9942 let (row_diff, arrow) = if target_row < current_row {
9943 (current_row - target_row, IconName::ArrowUp)
9944 } else {
9945 (target_row - current_row, IconName::ArrowDown)
9946 };
9947
9948 h_flex()
9949 .child(
9950 Label::new(format!("{}{}", prefix.into(), row_diff))
9951 .color(Color::Muted)
9952 .size(LabelSize::Small),
9953 )
9954 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9955 }
9956
9957 let supports_jump = self
9958 .edit_prediction_provider
9959 .as_ref()
9960 .map(|provider| provider.provider.supports_jump_to_edit())
9961 .unwrap_or(true);
9962
9963 match &completion.completion {
9964 EditPrediction::MoveWithin {
9965 target, snapshot, ..
9966 } => {
9967 if !supports_jump {
9968 return None;
9969 }
9970
9971 Some(
9972 h_flex()
9973 .px_2()
9974 .gap_2()
9975 .flex_1()
9976 .child(
9977 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9978 Icon::new(IconName::ZedPredictDown)
9979 } else {
9980 Icon::new(IconName::ZedPredictUp)
9981 },
9982 )
9983 .child(Label::new("Jump to Edit")),
9984 )
9985 }
9986 EditPrediction::MoveOutside { snapshot, .. } => {
9987 let file_name = snapshot
9988 .file()
9989 .map(|file| file.file_name(cx))
9990 .unwrap_or("untitled");
9991 Some(
9992 h_flex()
9993 .px_2()
9994 .gap_2()
9995 .flex_1()
9996 .child(Icon::new(IconName::ZedPredict))
9997 .child(Label::new(format!("Jump to {file_name}"))),
9998 )
9999 }
10000 EditPrediction::Edit {
10001 edits,
10002 edit_preview,
10003 snapshot,
10004 display_mode: _,
10005 } => {
10006 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10007
10008 let (highlighted_edits, has_more_lines) =
10009 if let Some(edit_preview) = edit_preview.as_ref() {
10010 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10011 .first_line_preview()
10012 } else {
10013 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10014 };
10015
10016 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10017 .with_default_highlights(&style.text, highlighted_edits.highlights);
10018
10019 let preview = h_flex()
10020 .gap_1()
10021 .min_w_16()
10022 .child(styled_text)
10023 .when(has_more_lines, |parent| parent.child("…"));
10024
10025 let left = if supports_jump && first_edit_row != cursor_point.row {
10026 render_relative_row_jump("", cursor_point.row, first_edit_row)
10027 .into_any_element()
10028 } else {
10029 let icon_name =
10030 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
10031 Icon::new(icon_name).into_any_element()
10032 };
10033
10034 Some(
10035 h_flex()
10036 .h_full()
10037 .flex_1()
10038 .gap_2()
10039 .pr_1()
10040 .overflow_x_hidden()
10041 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10042 .child(left)
10043 .child(preview),
10044 )
10045 }
10046 }
10047 }
10048
10049 pub fn render_context_menu(
10050 &mut self,
10051 max_height_in_lines: u32,
10052 window: &mut Window,
10053 cx: &mut Context<Editor>,
10054 ) -> Option<AnyElement> {
10055 let menu = self.context_menu.borrow();
10056 let menu = menu.as_ref()?;
10057 if !menu.visible() {
10058 return None;
10059 };
10060 self.style
10061 .as_ref()
10062 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10063 }
10064
10065 fn render_context_menu_aside(
10066 &mut self,
10067 max_size: Size<Pixels>,
10068 window: &mut Window,
10069 cx: &mut Context<Editor>,
10070 ) -> Option<AnyElement> {
10071 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10072 if menu.visible() {
10073 menu.render_aside(max_size, window, cx)
10074 } else {
10075 None
10076 }
10077 })
10078 }
10079
10080 fn hide_context_menu(
10081 &mut self,
10082 window: &mut Window,
10083 cx: &mut Context<Self>,
10084 ) -> Option<CodeContextMenu> {
10085 cx.notify();
10086 self.completion_tasks.clear();
10087 let context_menu = self.context_menu.borrow_mut().take();
10088 self.stale_edit_prediction_in_menu.take();
10089 self.update_visible_edit_prediction(window, cx);
10090 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10091 && let Some(completion_provider) = &self.completion_provider
10092 {
10093 completion_provider.selection_changed(None, window, cx);
10094 }
10095 context_menu
10096 }
10097
10098 fn show_snippet_choices(
10099 &mut self,
10100 choices: &Vec<String>,
10101 selection: Range<Anchor>,
10102 cx: &mut Context<Self>,
10103 ) {
10104 let Some((_, buffer, _)) = self
10105 .buffer()
10106 .read(cx)
10107 .excerpt_containing(selection.start, cx)
10108 else {
10109 return;
10110 };
10111 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10112 else {
10113 return;
10114 };
10115 if buffer != end_buffer {
10116 log::error!("expected anchor range to have matching buffer IDs");
10117 return;
10118 }
10119
10120 let id = post_inc(&mut self.next_completion_id);
10121 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10122 let mut context_menu = self.context_menu.borrow_mut();
10123 let old_menu = context_menu.take();
10124 *context_menu = Some(CodeContextMenu::Completions(
10125 CompletionsMenu::new_snippet_choices(
10126 id,
10127 true,
10128 choices,
10129 selection,
10130 buffer,
10131 old_menu.map(|menu| menu.primary_scroll_handle()),
10132 snippet_sort_order,
10133 ),
10134 ));
10135 }
10136
10137 pub fn insert_snippet(
10138 &mut self,
10139 insertion_ranges: &[Range<MultiBufferOffset>],
10140 snippet: Snippet,
10141 window: &mut Window,
10142 cx: &mut Context<Self>,
10143 ) -> Result<()> {
10144 struct Tabstop<T> {
10145 is_end_tabstop: bool,
10146 ranges: Vec<Range<T>>,
10147 choices: Option<Vec<String>>,
10148 }
10149
10150 let tabstops = self.buffer.update(cx, |buffer, cx| {
10151 let snippet_text: Arc<str> = snippet.text.clone().into();
10152 let edits = insertion_ranges
10153 .iter()
10154 .cloned()
10155 .map(|range| (range, snippet_text.clone()));
10156 let autoindent_mode = AutoindentMode::Block {
10157 original_indent_columns: Vec::new(),
10158 };
10159 buffer.edit(edits, Some(autoindent_mode), cx);
10160
10161 let snapshot = &*buffer.read(cx);
10162 let snippet = &snippet;
10163 snippet
10164 .tabstops
10165 .iter()
10166 .map(|tabstop| {
10167 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10168 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10169 });
10170 let mut tabstop_ranges = tabstop
10171 .ranges
10172 .iter()
10173 .flat_map(|tabstop_range| {
10174 let mut delta = 0_isize;
10175 insertion_ranges.iter().map(move |insertion_range| {
10176 let insertion_start = insertion_range.start + delta;
10177 delta += snippet.text.len() as isize
10178 - (insertion_range.end - insertion_range.start) as isize;
10179
10180 let start =
10181 (insertion_start + tabstop_range.start).min(snapshot.len());
10182 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10183 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10184 })
10185 })
10186 .collect::<Vec<_>>();
10187 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10188
10189 Tabstop {
10190 is_end_tabstop,
10191 ranges: tabstop_ranges,
10192 choices: tabstop.choices.clone(),
10193 }
10194 })
10195 .collect::<Vec<_>>()
10196 });
10197 if let Some(tabstop) = tabstops.first() {
10198 self.change_selections(Default::default(), window, cx, |s| {
10199 // Reverse order so that the first range is the newest created selection.
10200 // Completions will use it and autoscroll will prioritize it.
10201 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10202 });
10203
10204 if let Some(choices) = &tabstop.choices
10205 && let Some(selection) = tabstop.ranges.first()
10206 {
10207 self.show_snippet_choices(choices, selection.clone(), cx)
10208 }
10209
10210 // If we're already at the last tabstop and it's at the end of the snippet,
10211 // we're done, we don't need to keep the state around.
10212 if !tabstop.is_end_tabstop {
10213 let choices = tabstops
10214 .iter()
10215 .map(|tabstop| tabstop.choices.clone())
10216 .collect();
10217
10218 let ranges = tabstops
10219 .into_iter()
10220 .map(|tabstop| tabstop.ranges)
10221 .collect::<Vec<_>>();
10222
10223 self.snippet_stack.push(SnippetState {
10224 active_index: 0,
10225 ranges,
10226 choices,
10227 });
10228 }
10229
10230 // Check whether the just-entered snippet ends with an auto-closable bracket.
10231 if self.autoclose_regions.is_empty() {
10232 let snapshot = self.buffer.read(cx).snapshot(cx);
10233 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10234 let selection_head = selection.head();
10235 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10236 continue;
10237 };
10238
10239 let mut bracket_pair = None;
10240 let max_lookup_length = scope
10241 .brackets()
10242 .map(|(pair, _)| {
10243 pair.start
10244 .as_str()
10245 .chars()
10246 .count()
10247 .max(pair.end.as_str().chars().count())
10248 })
10249 .max();
10250 if let Some(max_lookup_length) = max_lookup_length {
10251 let next_text = snapshot
10252 .chars_at(selection_head)
10253 .take(max_lookup_length)
10254 .collect::<String>();
10255 let prev_text = snapshot
10256 .reversed_chars_at(selection_head)
10257 .take(max_lookup_length)
10258 .collect::<String>();
10259
10260 for (pair, enabled) in scope.brackets() {
10261 if enabled
10262 && pair.close
10263 && prev_text.starts_with(pair.start.as_str())
10264 && next_text.starts_with(pair.end.as_str())
10265 {
10266 bracket_pair = Some(pair.clone());
10267 break;
10268 }
10269 }
10270 }
10271
10272 if let Some(pair) = bracket_pair {
10273 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10274 let autoclose_enabled =
10275 self.use_autoclose && snapshot_settings.use_autoclose;
10276 if autoclose_enabled {
10277 let start = snapshot.anchor_after(selection_head);
10278 let end = snapshot.anchor_after(selection_head);
10279 self.autoclose_regions.push(AutocloseRegion {
10280 selection_id: selection.id,
10281 range: start..end,
10282 pair,
10283 });
10284 }
10285 }
10286 }
10287 }
10288 }
10289 Ok(())
10290 }
10291
10292 pub fn move_to_next_snippet_tabstop(
10293 &mut self,
10294 window: &mut Window,
10295 cx: &mut Context<Self>,
10296 ) -> bool {
10297 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10298 }
10299
10300 pub fn move_to_prev_snippet_tabstop(
10301 &mut self,
10302 window: &mut Window,
10303 cx: &mut Context<Self>,
10304 ) -> bool {
10305 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10306 }
10307
10308 pub fn move_to_snippet_tabstop(
10309 &mut self,
10310 bias: Bias,
10311 window: &mut Window,
10312 cx: &mut Context<Self>,
10313 ) -> bool {
10314 if let Some(mut snippet) = self.snippet_stack.pop() {
10315 match bias {
10316 Bias::Left => {
10317 if snippet.active_index > 0 {
10318 snippet.active_index -= 1;
10319 } else {
10320 self.snippet_stack.push(snippet);
10321 return false;
10322 }
10323 }
10324 Bias::Right => {
10325 if snippet.active_index + 1 < snippet.ranges.len() {
10326 snippet.active_index += 1;
10327 } else {
10328 self.snippet_stack.push(snippet);
10329 return false;
10330 }
10331 }
10332 }
10333 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10334 self.change_selections(Default::default(), window, cx, |s| {
10335 // Reverse order so that the first range is the newest created selection.
10336 // Completions will use it and autoscroll will prioritize it.
10337 s.select_ranges(current_ranges.iter().rev().cloned())
10338 });
10339
10340 if let Some(choices) = &snippet.choices[snippet.active_index]
10341 && let Some(selection) = current_ranges.first()
10342 {
10343 self.show_snippet_choices(choices, selection.clone(), cx);
10344 }
10345
10346 // If snippet state is not at the last tabstop, push it back on the stack
10347 if snippet.active_index + 1 < snippet.ranges.len() {
10348 self.snippet_stack.push(snippet);
10349 }
10350 return true;
10351 }
10352 }
10353
10354 false
10355 }
10356
10357 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10358 self.transact(window, cx, |this, window, cx| {
10359 this.select_all(&SelectAll, window, cx);
10360 this.insert("", window, cx);
10361 });
10362 }
10363
10364 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10365 if self.read_only(cx) {
10366 return;
10367 }
10368 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10369 self.transact(window, cx, |this, window, cx| {
10370 this.select_autoclose_pair(window, cx);
10371
10372 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10373
10374 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10375 if !this.linked_edit_ranges.is_empty() {
10376 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10377 let snapshot = this.buffer.read(cx).snapshot(cx);
10378
10379 for selection in selections.iter() {
10380 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10381 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10382 if selection_start.buffer_id != selection_end.buffer_id {
10383 continue;
10384 }
10385 if let Some(ranges) =
10386 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10387 {
10388 for (buffer, entries) in ranges {
10389 linked_ranges.entry(buffer).or_default().extend(entries);
10390 }
10391 }
10392 }
10393 }
10394
10395 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10396 for selection in &mut selections {
10397 if selection.is_empty() {
10398 let old_head = selection.head();
10399 let mut new_head =
10400 movement::left(&display_map, old_head.to_display_point(&display_map))
10401 .to_point(&display_map);
10402 if let Some((buffer, line_buffer_range)) = display_map
10403 .buffer_snapshot()
10404 .buffer_line_for_row(MultiBufferRow(old_head.row))
10405 {
10406 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10407 let indent_len = match indent_size.kind {
10408 IndentKind::Space => {
10409 buffer.settings_at(line_buffer_range.start, cx).tab_size
10410 }
10411 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10412 };
10413 if old_head.column <= indent_size.len && old_head.column > 0 {
10414 let indent_len = indent_len.get();
10415 new_head = cmp::min(
10416 new_head,
10417 MultiBufferPoint::new(
10418 old_head.row,
10419 ((old_head.column - 1) / indent_len) * indent_len,
10420 ),
10421 );
10422 }
10423 }
10424
10425 selection.set_head(new_head, SelectionGoal::None);
10426 }
10427 }
10428
10429 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10430 this.insert("", window, cx);
10431 let empty_str: Arc<str> = Arc::from("");
10432 for (buffer, edits) in linked_ranges {
10433 let snapshot = buffer.read(cx).snapshot();
10434 use text::ToPoint as TP;
10435
10436 let edits = edits
10437 .into_iter()
10438 .map(|range| {
10439 let end_point = TP::to_point(&range.end, &snapshot);
10440 let mut start_point = TP::to_point(&range.start, &snapshot);
10441
10442 if end_point == start_point {
10443 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10444 .saturating_sub(1);
10445 start_point =
10446 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10447 };
10448
10449 (start_point..end_point, empty_str.clone())
10450 })
10451 .sorted_by_key(|(range, _)| range.start)
10452 .collect::<Vec<_>>();
10453 buffer.update(cx, |this, cx| {
10454 this.edit(edits, None, cx);
10455 })
10456 }
10457 this.refresh_edit_prediction(true, false, window, cx);
10458 refresh_linked_ranges(this, window, cx);
10459 });
10460 }
10461
10462 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10463 if self.read_only(cx) {
10464 return;
10465 }
10466 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10467 self.transact(window, cx, |this, window, cx| {
10468 this.change_selections(Default::default(), window, cx, |s| {
10469 s.move_with(|map, selection| {
10470 if selection.is_empty() {
10471 let cursor = movement::right(map, selection.head());
10472 selection.end = cursor;
10473 selection.reversed = true;
10474 selection.goal = SelectionGoal::None;
10475 }
10476 })
10477 });
10478 this.insert("", window, cx);
10479 this.refresh_edit_prediction(true, false, window, cx);
10480 });
10481 }
10482
10483 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10484 if self.mode.is_single_line() {
10485 cx.propagate();
10486 return;
10487 }
10488
10489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10490 if self.move_to_prev_snippet_tabstop(window, cx) {
10491 return;
10492 }
10493 self.outdent(&Outdent, window, cx);
10494 }
10495
10496 pub fn next_snippet_tabstop(
10497 &mut self,
10498 _: &NextSnippetTabstop,
10499 window: &mut Window,
10500 cx: &mut Context<Self>,
10501 ) {
10502 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10503 cx.propagate();
10504 return;
10505 }
10506
10507 if self.move_to_next_snippet_tabstop(window, cx) {
10508 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10509 return;
10510 }
10511 cx.propagate();
10512 }
10513
10514 pub fn previous_snippet_tabstop(
10515 &mut self,
10516 _: &PreviousSnippetTabstop,
10517 window: &mut Window,
10518 cx: &mut Context<Self>,
10519 ) {
10520 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10521 cx.propagate();
10522 return;
10523 }
10524
10525 if self.move_to_prev_snippet_tabstop(window, cx) {
10526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10527 return;
10528 }
10529 cx.propagate();
10530 }
10531
10532 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10533 if self.mode.is_single_line() {
10534 cx.propagate();
10535 return;
10536 }
10537
10538 if self.move_to_next_snippet_tabstop(window, cx) {
10539 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10540 return;
10541 }
10542 if self.read_only(cx) {
10543 return;
10544 }
10545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10546 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10547 let buffer = self.buffer.read(cx);
10548 let snapshot = buffer.snapshot(cx);
10549 let rows_iter = selections.iter().map(|s| s.head().row);
10550 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10551
10552 let has_some_cursor_in_whitespace = selections
10553 .iter()
10554 .filter(|selection| selection.is_empty())
10555 .any(|selection| {
10556 let cursor = selection.head();
10557 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10558 cursor.column < current_indent.len
10559 });
10560
10561 let mut edits = Vec::new();
10562 let mut prev_edited_row = 0;
10563 let mut row_delta = 0;
10564 for selection in &mut selections {
10565 if selection.start.row != prev_edited_row {
10566 row_delta = 0;
10567 }
10568 prev_edited_row = selection.end.row;
10569
10570 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10571 if selection.is_empty() {
10572 let cursor = selection.head();
10573 let settings = buffer.language_settings_at(cursor, cx);
10574 if settings.indent_list_on_tab {
10575 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10576 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10577 row_delta = Self::indent_selection(
10578 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10579 );
10580 continue;
10581 }
10582 }
10583 }
10584 }
10585
10586 // If the selection is non-empty, then increase the indentation of the selected lines.
10587 if !selection.is_empty() {
10588 row_delta =
10589 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10590 continue;
10591 }
10592
10593 let cursor = selection.head();
10594 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10595 if let Some(suggested_indent) =
10596 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10597 {
10598 // Don't do anything if already at suggested indent
10599 // and there is any other cursor which is not
10600 if has_some_cursor_in_whitespace
10601 && cursor.column == current_indent.len
10602 && current_indent.len == suggested_indent.len
10603 {
10604 continue;
10605 }
10606
10607 // Adjust line and move cursor to suggested indent
10608 // if cursor is not at suggested indent
10609 if cursor.column < suggested_indent.len
10610 && cursor.column <= current_indent.len
10611 && current_indent.len <= suggested_indent.len
10612 {
10613 selection.start = Point::new(cursor.row, suggested_indent.len);
10614 selection.end = selection.start;
10615 if row_delta == 0 {
10616 edits.extend(Buffer::edit_for_indent_size_adjustment(
10617 cursor.row,
10618 current_indent,
10619 suggested_indent,
10620 ));
10621 row_delta = suggested_indent.len - current_indent.len;
10622 }
10623 continue;
10624 }
10625
10626 // If current indent is more than suggested indent
10627 // only move cursor to current indent and skip indent
10628 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10629 selection.start = Point::new(cursor.row, current_indent.len);
10630 selection.end = selection.start;
10631 continue;
10632 }
10633 }
10634
10635 // Otherwise, insert a hard or soft tab.
10636 let settings = buffer.language_settings_at(cursor, cx);
10637 let tab_size = if settings.hard_tabs {
10638 IndentSize::tab()
10639 } else {
10640 let tab_size = settings.tab_size.get();
10641 let indent_remainder = snapshot
10642 .text_for_range(Point::new(cursor.row, 0)..cursor)
10643 .flat_map(str::chars)
10644 .fold(row_delta % tab_size, |counter: u32, c| {
10645 if c == '\t' {
10646 0
10647 } else {
10648 (counter + 1) % tab_size
10649 }
10650 });
10651
10652 let chars_to_next_tab_stop = tab_size - indent_remainder;
10653 IndentSize::spaces(chars_to_next_tab_stop)
10654 };
10655 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10656 selection.end = selection.start;
10657 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10658 row_delta += tab_size.len;
10659 }
10660
10661 self.transact(window, cx, |this, window, cx| {
10662 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10663 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10664 this.refresh_edit_prediction(true, false, window, cx);
10665 });
10666 }
10667
10668 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10669 if self.read_only(cx) {
10670 return;
10671 }
10672 if self.mode.is_single_line() {
10673 cx.propagate();
10674 return;
10675 }
10676
10677 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10678 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10679 let mut prev_edited_row = 0;
10680 let mut row_delta = 0;
10681 let mut edits = Vec::new();
10682 let buffer = self.buffer.read(cx);
10683 let snapshot = buffer.snapshot(cx);
10684 for selection in &mut selections {
10685 if selection.start.row != prev_edited_row {
10686 row_delta = 0;
10687 }
10688 prev_edited_row = selection.end.row;
10689
10690 row_delta =
10691 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10692 }
10693
10694 self.transact(window, cx, |this, window, cx| {
10695 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10696 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10697 });
10698 }
10699
10700 fn indent_selection(
10701 buffer: &MultiBuffer,
10702 snapshot: &MultiBufferSnapshot,
10703 selection: &mut Selection<Point>,
10704 edits: &mut Vec<(Range<Point>, String)>,
10705 delta_for_start_row: u32,
10706 cx: &App,
10707 ) -> u32 {
10708 let settings = buffer.language_settings_at(selection.start, cx);
10709 let tab_size = settings.tab_size.get();
10710 let indent_kind = if settings.hard_tabs {
10711 IndentKind::Tab
10712 } else {
10713 IndentKind::Space
10714 };
10715 let mut start_row = selection.start.row;
10716 let mut end_row = selection.end.row + 1;
10717
10718 // If a selection ends at the beginning of a line, don't indent
10719 // that last line.
10720 if selection.end.column == 0 && selection.end.row > selection.start.row {
10721 end_row -= 1;
10722 }
10723
10724 // Avoid re-indenting a row that has already been indented by a
10725 // previous selection, but still update this selection's column
10726 // to reflect that indentation.
10727 if delta_for_start_row > 0 {
10728 start_row += 1;
10729 selection.start.column += delta_for_start_row;
10730 if selection.end.row == selection.start.row {
10731 selection.end.column += delta_for_start_row;
10732 }
10733 }
10734
10735 let mut delta_for_end_row = 0;
10736 let has_multiple_rows = start_row + 1 != end_row;
10737 for row in start_row..end_row {
10738 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10739 let indent_delta = match (current_indent.kind, indent_kind) {
10740 (IndentKind::Space, IndentKind::Space) => {
10741 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10742 IndentSize::spaces(columns_to_next_tab_stop)
10743 }
10744 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10745 (_, IndentKind::Tab) => IndentSize::tab(),
10746 };
10747
10748 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10749 0
10750 } else {
10751 selection.start.column
10752 };
10753 let row_start = Point::new(row, start);
10754 edits.push((
10755 row_start..row_start,
10756 indent_delta.chars().collect::<String>(),
10757 ));
10758
10759 // Update this selection's endpoints to reflect the indentation.
10760 if row == selection.start.row {
10761 selection.start.column += indent_delta.len;
10762 }
10763 if row == selection.end.row {
10764 selection.end.column += indent_delta.len;
10765 delta_for_end_row = indent_delta.len;
10766 }
10767 }
10768
10769 if selection.start.row == selection.end.row {
10770 delta_for_start_row + delta_for_end_row
10771 } else {
10772 delta_for_end_row
10773 }
10774 }
10775
10776 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10777 if self.read_only(cx) {
10778 return;
10779 }
10780 if self.mode.is_single_line() {
10781 cx.propagate();
10782 return;
10783 }
10784
10785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10786 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10787 let selections = self.selections.all::<Point>(&display_map);
10788 let mut deletion_ranges = Vec::new();
10789 let mut last_outdent = None;
10790 {
10791 let buffer = self.buffer.read(cx);
10792 let snapshot = buffer.snapshot(cx);
10793 for selection in &selections {
10794 let settings = buffer.language_settings_at(selection.start, cx);
10795 let tab_size = settings.tab_size.get();
10796 let mut rows = selection.spanned_rows(false, &display_map);
10797
10798 // Avoid re-outdenting a row that has already been outdented by a
10799 // previous selection.
10800 if let Some(last_row) = last_outdent
10801 && last_row == rows.start
10802 {
10803 rows.start = rows.start.next_row();
10804 }
10805 let has_multiple_rows = rows.len() > 1;
10806 for row in rows.iter_rows() {
10807 let indent_size = snapshot.indent_size_for_line(row);
10808 if indent_size.len > 0 {
10809 let deletion_len = match indent_size.kind {
10810 IndentKind::Space => {
10811 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10812 if columns_to_prev_tab_stop == 0 {
10813 tab_size
10814 } else {
10815 columns_to_prev_tab_stop
10816 }
10817 }
10818 IndentKind::Tab => 1,
10819 };
10820 let start = if has_multiple_rows
10821 || deletion_len > selection.start.column
10822 || indent_size.len < selection.start.column
10823 {
10824 0
10825 } else {
10826 selection.start.column - deletion_len
10827 };
10828 deletion_ranges.push(
10829 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10830 );
10831 last_outdent = Some(row);
10832 }
10833 }
10834 }
10835 }
10836
10837 self.transact(window, cx, |this, window, cx| {
10838 this.buffer.update(cx, |buffer, cx| {
10839 let empty_str: Arc<str> = Arc::default();
10840 buffer.edit(
10841 deletion_ranges
10842 .into_iter()
10843 .map(|range| (range, empty_str.clone())),
10844 None,
10845 cx,
10846 );
10847 });
10848 let selections = this
10849 .selections
10850 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10851 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10852 });
10853 }
10854
10855 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10856 if self.read_only(cx) {
10857 return;
10858 }
10859 if self.mode.is_single_line() {
10860 cx.propagate();
10861 return;
10862 }
10863
10864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10865 let selections = self
10866 .selections
10867 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10868 .into_iter()
10869 .map(|s| s.range());
10870
10871 self.transact(window, cx, |this, window, cx| {
10872 this.buffer.update(cx, |buffer, cx| {
10873 buffer.autoindent_ranges(selections, cx);
10874 });
10875 let selections = this
10876 .selections
10877 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10878 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10879 });
10880 }
10881
10882 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10883 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10885 let selections = self.selections.all::<Point>(&display_map);
10886
10887 let mut new_cursors = Vec::new();
10888 let mut edit_ranges = Vec::new();
10889 let mut selections = selections.iter().peekable();
10890 while let Some(selection) = selections.next() {
10891 let mut rows = selection.spanned_rows(false, &display_map);
10892
10893 // Accumulate contiguous regions of rows that we want to delete.
10894 while let Some(next_selection) = selections.peek() {
10895 let next_rows = next_selection.spanned_rows(false, &display_map);
10896 if next_rows.start <= rows.end {
10897 rows.end = next_rows.end;
10898 selections.next().unwrap();
10899 } else {
10900 break;
10901 }
10902 }
10903
10904 let buffer = display_map.buffer_snapshot();
10905 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10906 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10907 // If there's a line after the range, delete the \n from the end of the row range
10908 (
10909 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10910 rows.end,
10911 )
10912 } else {
10913 // If there isn't a line after the range, delete the \n from the line before the
10914 // start of the row range
10915 edit_start = edit_start.saturating_sub_usize(1);
10916 (buffer.len(), rows.start.previous_row())
10917 };
10918
10919 let text_layout_details = self.text_layout_details(window);
10920 let x = display_map.x_for_display_point(
10921 selection.head().to_display_point(&display_map),
10922 &text_layout_details,
10923 );
10924 let row = Point::new(target_row.0, 0)
10925 .to_display_point(&display_map)
10926 .row();
10927 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10928
10929 new_cursors.push((
10930 selection.id,
10931 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10932 SelectionGoal::None,
10933 ));
10934 edit_ranges.push(edit_start..edit_end);
10935 }
10936
10937 self.transact(window, cx, |this, window, cx| {
10938 let buffer = this.buffer.update(cx, |buffer, cx| {
10939 let empty_str: Arc<str> = Arc::default();
10940 buffer.edit(
10941 edit_ranges
10942 .into_iter()
10943 .map(|range| (range, empty_str.clone())),
10944 None,
10945 cx,
10946 );
10947 buffer.snapshot(cx)
10948 });
10949 let new_selections = new_cursors
10950 .into_iter()
10951 .map(|(id, cursor, goal)| {
10952 let cursor = cursor.to_point(&buffer);
10953 Selection {
10954 id,
10955 start: cursor,
10956 end: cursor,
10957 reversed: false,
10958 goal,
10959 }
10960 })
10961 .collect();
10962
10963 this.change_selections(Default::default(), window, cx, |s| {
10964 s.select(new_selections);
10965 });
10966 });
10967 }
10968
10969 pub fn join_lines_impl(
10970 &mut self,
10971 insert_whitespace: bool,
10972 window: &mut Window,
10973 cx: &mut Context<Self>,
10974 ) {
10975 if self.read_only(cx) {
10976 return;
10977 }
10978 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10979 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10980 let start = MultiBufferRow(selection.start.row);
10981 // Treat single line selections as if they include the next line. Otherwise this action
10982 // would do nothing for single line selections individual cursors.
10983 let end = if selection.start.row == selection.end.row {
10984 MultiBufferRow(selection.start.row + 1)
10985 } else {
10986 MultiBufferRow(selection.end.row)
10987 };
10988
10989 if let Some(last_row_range) = row_ranges.last_mut()
10990 && start <= last_row_range.end
10991 {
10992 last_row_range.end = end;
10993 continue;
10994 }
10995 row_ranges.push(start..end);
10996 }
10997
10998 let snapshot = self.buffer.read(cx).snapshot(cx);
10999 let mut cursor_positions = Vec::new();
11000 for row_range in &row_ranges {
11001 let anchor = snapshot.anchor_before(Point::new(
11002 row_range.end.previous_row().0,
11003 snapshot.line_len(row_range.end.previous_row()),
11004 ));
11005 cursor_positions.push(anchor..anchor);
11006 }
11007
11008 self.transact(window, cx, |this, window, cx| {
11009 for row_range in row_ranges.into_iter().rev() {
11010 for row in row_range.iter_rows().rev() {
11011 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11012 let next_line_row = row.next_row();
11013 let indent = snapshot.indent_size_for_line(next_line_row);
11014 let start_of_next_line = Point::new(next_line_row.0, indent.len);
11015
11016 let replace =
11017 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
11018 " "
11019 } else {
11020 ""
11021 };
11022
11023 this.buffer.update(cx, |buffer, cx| {
11024 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11025 });
11026 }
11027 }
11028
11029 this.change_selections(Default::default(), window, cx, |s| {
11030 s.select_anchor_ranges(cursor_positions)
11031 });
11032 });
11033 }
11034
11035 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11037 self.join_lines_impl(true, window, cx);
11038 }
11039
11040 pub fn sort_lines_case_sensitive(
11041 &mut self,
11042 _: &SortLinesCaseSensitive,
11043 window: &mut Window,
11044 cx: &mut Context<Self>,
11045 ) {
11046 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11047 }
11048
11049 pub fn sort_lines_by_length(
11050 &mut self,
11051 _: &SortLinesByLength,
11052 window: &mut Window,
11053 cx: &mut Context<Self>,
11054 ) {
11055 self.manipulate_immutable_lines(window, cx, |lines| {
11056 lines.sort_by_key(|&line| line.chars().count())
11057 })
11058 }
11059
11060 pub fn sort_lines_case_insensitive(
11061 &mut self,
11062 _: &SortLinesCaseInsensitive,
11063 window: &mut Window,
11064 cx: &mut Context<Self>,
11065 ) {
11066 self.manipulate_immutable_lines(window, cx, |lines| {
11067 lines.sort_by_key(|line| line.to_lowercase())
11068 })
11069 }
11070
11071 pub fn unique_lines_case_insensitive(
11072 &mut self,
11073 _: &UniqueLinesCaseInsensitive,
11074 window: &mut Window,
11075 cx: &mut Context<Self>,
11076 ) {
11077 self.manipulate_immutable_lines(window, cx, |lines| {
11078 let mut seen = HashSet::default();
11079 lines.retain(|line| seen.insert(line.to_lowercase()));
11080 })
11081 }
11082
11083 pub fn unique_lines_case_sensitive(
11084 &mut self,
11085 _: &UniqueLinesCaseSensitive,
11086 window: &mut Window,
11087 cx: &mut Context<Self>,
11088 ) {
11089 self.manipulate_immutable_lines(window, cx, |lines| {
11090 let mut seen = HashSet::default();
11091 lines.retain(|line| seen.insert(*line));
11092 })
11093 }
11094
11095 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11096 let snapshot = self.buffer.read(cx).snapshot(cx);
11097 for selection in self.selections.disjoint_anchors_arc().iter() {
11098 if snapshot
11099 .language_at(selection.start)
11100 .and_then(|lang| lang.config().wrap_characters.as_ref())
11101 .is_some()
11102 {
11103 return true;
11104 }
11105 }
11106 false
11107 }
11108
11109 fn wrap_selections_in_tag(
11110 &mut self,
11111 _: &WrapSelectionsInTag,
11112 window: &mut Window,
11113 cx: &mut Context<Self>,
11114 ) {
11115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11116
11117 let snapshot = self.buffer.read(cx).snapshot(cx);
11118
11119 let mut edits = Vec::new();
11120 let mut boundaries = Vec::new();
11121
11122 for selection in self
11123 .selections
11124 .all_adjusted(&self.display_snapshot(cx))
11125 .iter()
11126 {
11127 let Some(wrap_config) = snapshot
11128 .language_at(selection.start)
11129 .and_then(|lang| lang.config().wrap_characters.clone())
11130 else {
11131 continue;
11132 };
11133
11134 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11135 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11136
11137 let start_before = snapshot.anchor_before(selection.start);
11138 let end_after = snapshot.anchor_after(selection.end);
11139
11140 edits.push((start_before..start_before, open_tag));
11141 edits.push((end_after..end_after, close_tag));
11142
11143 boundaries.push((
11144 start_before,
11145 end_after,
11146 wrap_config.start_prefix.len(),
11147 wrap_config.end_suffix.len(),
11148 ));
11149 }
11150
11151 if edits.is_empty() {
11152 return;
11153 }
11154
11155 self.transact(window, cx, |this, window, cx| {
11156 let buffer = this.buffer.update(cx, |buffer, cx| {
11157 buffer.edit(edits, None, cx);
11158 buffer.snapshot(cx)
11159 });
11160
11161 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11162 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11163 boundaries.into_iter()
11164 {
11165 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11166 let close_offset = end_after
11167 .to_offset(&buffer)
11168 .saturating_sub_usize(end_suffix_len);
11169 new_selections.push(open_offset..open_offset);
11170 new_selections.push(close_offset..close_offset);
11171 }
11172
11173 this.change_selections(Default::default(), window, cx, |s| {
11174 s.select_ranges(new_selections);
11175 });
11176
11177 this.request_autoscroll(Autoscroll::fit(), cx);
11178 });
11179 }
11180
11181 pub fn toggle_read_only(
11182 &mut self,
11183 _: &workspace::ToggleReadOnlyFile,
11184 _: &mut Window,
11185 cx: &mut Context<Self>,
11186 ) {
11187 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11188 buffer.update(cx, |buffer, cx| {
11189 buffer.set_capability(
11190 match buffer.capability() {
11191 Capability::ReadWrite => Capability::Read,
11192 Capability::Read => Capability::ReadWrite,
11193 Capability::ReadOnly => Capability::ReadOnly,
11194 },
11195 cx,
11196 );
11197 })
11198 }
11199 }
11200
11201 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11202 let Some(project) = self.project.clone() else {
11203 return;
11204 };
11205 self.reload(project, window, cx)
11206 .detach_and_notify_err(window, cx);
11207 }
11208
11209 pub fn restore_file(
11210 &mut self,
11211 _: &::git::RestoreFile,
11212 window: &mut Window,
11213 cx: &mut Context<Self>,
11214 ) {
11215 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11216 let mut buffer_ids = HashSet::default();
11217 let snapshot = self.buffer().read(cx).snapshot(cx);
11218 for selection in self
11219 .selections
11220 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11221 {
11222 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11223 }
11224
11225 let buffer = self.buffer().read(cx);
11226 let ranges = buffer_ids
11227 .into_iter()
11228 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11229 .collect::<Vec<_>>();
11230
11231 self.restore_hunks_in_ranges(ranges, window, cx);
11232 }
11233
11234 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11236 let selections = self
11237 .selections
11238 .all(&self.display_snapshot(cx))
11239 .into_iter()
11240 .map(|s| s.range())
11241 .collect();
11242 self.restore_hunks_in_ranges(selections, window, cx);
11243 }
11244
11245 pub fn restore_hunks_in_ranges(
11246 &mut self,
11247 ranges: Vec<Range<Point>>,
11248 window: &mut Window,
11249 cx: &mut Context<Editor>,
11250 ) {
11251 let mut revert_changes = HashMap::default();
11252 let chunk_by = self
11253 .snapshot(window, cx)
11254 .hunks_for_ranges(ranges)
11255 .into_iter()
11256 .chunk_by(|hunk| hunk.buffer_id);
11257 for (buffer_id, hunks) in &chunk_by {
11258 let hunks = hunks.collect::<Vec<_>>();
11259 for hunk in &hunks {
11260 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11261 }
11262 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11263 }
11264 drop(chunk_by);
11265 if !revert_changes.is_empty() {
11266 self.transact(window, cx, |editor, window, cx| {
11267 editor.restore(revert_changes, window, cx);
11268 });
11269 }
11270 }
11271
11272 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11273 if let Some(status) = self
11274 .addons
11275 .iter()
11276 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11277 {
11278 return Some(status);
11279 }
11280 self.project
11281 .as_ref()?
11282 .read(cx)
11283 .status_for_buffer_id(buffer_id, cx)
11284 }
11285
11286 pub fn open_active_item_in_terminal(
11287 &mut self,
11288 _: &OpenInTerminal,
11289 window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11293 let project_path = buffer.read(cx).project_path(cx)?;
11294 let project = self.project()?.read(cx);
11295 let entry = project.entry_for_path(&project_path, cx)?;
11296 let parent = match &entry.canonical_path {
11297 Some(canonical_path) => canonical_path.to_path_buf(),
11298 None => project.absolute_path(&project_path, cx)?,
11299 }
11300 .parent()?
11301 .to_path_buf();
11302 Some(parent)
11303 }) {
11304 window.dispatch_action(
11305 OpenTerminal {
11306 working_directory,
11307 local: false,
11308 }
11309 .boxed_clone(),
11310 cx,
11311 );
11312 }
11313 }
11314
11315 fn set_breakpoint_context_menu(
11316 &mut self,
11317 display_row: DisplayRow,
11318 position: Option<Anchor>,
11319 clicked_point: gpui::Point<Pixels>,
11320 window: &mut Window,
11321 cx: &mut Context<Self>,
11322 ) {
11323 let source = self
11324 .buffer
11325 .read(cx)
11326 .snapshot(cx)
11327 .anchor_before(Point::new(display_row.0, 0u32));
11328
11329 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11330
11331 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11332 self,
11333 source,
11334 clicked_point,
11335 context_menu,
11336 window,
11337 cx,
11338 );
11339 }
11340
11341 fn add_edit_breakpoint_block(
11342 &mut self,
11343 anchor: Anchor,
11344 breakpoint: &Breakpoint,
11345 edit_action: BreakpointPromptEditAction,
11346 window: &mut Window,
11347 cx: &mut Context<Self>,
11348 ) {
11349 let weak_editor = cx.weak_entity();
11350 let bp_prompt = cx.new(|cx| {
11351 BreakpointPromptEditor::new(
11352 weak_editor,
11353 anchor,
11354 breakpoint.clone(),
11355 edit_action,
11356 window,
11357 cx,
11358 )
11359 });
11360
11361 let height = bp_prompt.update(cx, |this, cx| {
11362 this.prompt
11363 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11364 });
11365 let cloned_prompt = bp_prompt.clone();
11366 let blocks = vec![BlockProperties {
11367 style: BlockStyle::Sticky,
11368 placement: BlockPlacement::Above(anchor),
11369 height: Some(height),
11370 render: Arc::new(move |cx| {
11371 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11372 cloned_prompt.clone().into_any_element()
11373 }),
11374 priority: 0,
11375 }];
11376
11377 let focus_handle = bp_prompt.focus_handle(cx);
11378 window.focus(&focus_handle, cx);
11379
11380 let block_ids = self.insert_blocks(blocks, None, cx);
11381 bp_prompt.update(cx, |prompt, _| {
11382 prompt.add_block_ids(block_ids);
11383 });
11384 }
11385
11386 pub(crate) fn breakpoint_at_row(
11387 &self,
11388 row: u32,
11389 window: &mut Window,
11390 cx: &mut Context<Self>,
11391 ) -> Option<(Anchor, Breakpoint)> {
11392 let snapshot = self.snapshot(window, cx);
11393 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11394
11395 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11396 }
11397
11398 pub(crate) fn breakpoint_at_anchor(
11399 &self,
11400 breakpoint_position: Anchor,
11401 snapshot: &EditorSnapshot,
11402 cx: &mut Context<Self>,
11403 ) -> Option<(Anchor, Breakpoint)> {
11404 let buffer = self
11405 .buffer
11406 .read(cx)
11407 .buffer_for_anchor(breakpoint_position, cx)?;
11408
11409 let enclosing_excerpt = breakpoint_position.excerpt_id;
11410 let buffer_snapshot = buffer.read(cx).snapshot();
11411
11412 let row = buffer_snapshot
11413 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11414 .row;
11415
11416 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11417 let anchor_end = snapshot
11418 .buffer_snapshot()
11419 .anchor_after(Point::new(row, line_len));
11420
11421 self.breakpoint_store
11422 .as_ref()?
11423 .read_with(cx, |breakpoint_store, cx| {
11424 breakpoint_store
11425 .breakpoints(
11426 &buffer,
11427 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11428 &buffer_snapshot,
11429 cx,
11430 )
11431 .next()
11432 .and_then(|(bp, _)| {
11433 let breakpoint_row = buffer_snapshot
11434 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11435 .row;
11436
11437 if breakpoint_row == row {
11438 snapshot
11439 .buffer_snapshot()
11440 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11441 .map(|position| (position, bp.bp.clone()))
11442 } else {
11443 None
11444 }
11445 })
11446 })
11447 }
11448
11449 pub fn edit_log_breakpoint(
11450 &mut self,
11451 _: &EditLogBreakpoint,
11452 window: &mut Window,
11453 cx: &mut Context<Self>,
11454 ) {
11455 if self.breakpoint_store.is_none() {
11456 return;
11457 }
11458
11459 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11460 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11461 message: None,
11462 state: BreakpointState::Enabled,
11463 condition: None,
11464 hit_condition: None,
11465 });
11466
11467 self.add_edit_breakpoint_block(
11468 anchor,
11469 &breakpoint,
11470 BreakpointPromptEditAction::Log,
11471 window,
11472 cx,
11473 );
11474 }
11475 }
11476
11477 fn breakpoints_at_cursors(
11478 &self,
11479 window: &mut Window,
11480 cx: &mut Context<Self>,
11481 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11482 let snapshot = self.snapshot(window, cx);
11483 let cursors = self
11484 .selections
11485 .disjoint_anchors_arc()
11486 .iter()
11487 .map(|selection| {
11488 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11489
11490 let breakpoint_position = self
11491 .breakpoint_at_row(cursor_position.row, window, cx)
11492 .map(|bp| bp.0)
11493 .unwrap_or_else(|| {
11494 snapshot
11495 .display_snapshot
11496 .buffer_snapshot()
11497 .anchor_after(Point::new(cursor_position.row, 0))
11498 });
11499
11500 let breakpoint = self
11501 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11502 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11503
11504 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11505 })
11506 // 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.
11507 .collect::<HashMap<Anchor, _>>();
11508
11509 cursors.into_iter().collect()
11510 }
11511
11512 pub fn enable_breakpoint(
11513 &mut self,
11514 _: &crate::actions::EnableBreakpoint,
11515 window: &mut Window,
11516 cx: &mut Context<Self>,
11517 ) {
11518 if self.breakpoint_store.is_none() {
11519 return;
11520 }
11521
11522 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11523 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11524 continue;
11525 };
11526 self.edit_breakpoint_at_anchor(
11527 anchor,
11528 breakpoint,
11529 BreakpointEditAction::InvertState,
11530 cx,
11531 );
11532 }
11533 }
11534
11535 pub fn disable_breakpoint(
11536 &mut self,
11537 _: &crate::actions::DisableBreakpoint,
11538 window: &mut Window,
11539 cx: &mut Context<Self>,
11540 ) {
11541 if self.breakpoint_store.is_none() {
11542 return;
11543 }
11544
11545 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11546 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11547 continue;
11548 };
11549 self.edit_breakpoint_at_anchor(
11550 anchor,
11551 breakpoint,
11552 BreakpointEditAction::InvertState,
11553 cx,
11554 );
11555 }
11556 }
11557
11558 pub fn toggle_breakpoint(
11559 &mut self,
11560 _: &crate::actions::ToggleBreakpoint,
11561 window: &mut Window,
11562 cx: &mut Context<Self>,
11563 ) {
11564 if self.breakpoint_store.is_none() {
11565 return;
11566 }
11567
11568 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11569 if let Some(breakpoint) = breakpoint {
11570 self.edit_breakpoint_at_anchor(
11571 anchor,
11572 breakpoint,
11573 BreakpointEditAction::Toggle,
11574 cx,
11575 );
11576 } else {
11577 self.edit_breakpoint_at_anchor(
11578 anchor,
11579 Breakpoint::new_standard(),
11580 BreakpointEditAction::Toggle,
11581 cx,
11582 );
11583 }
11584 }
11585 }
11586
11587 pub fn edit_breakpoint_at_anchor(
11588 &mut self,
11589 breakpoint_position: Anchor,
11590 breakpoint: Breakpoint,
11591 edit_action: BreakpointEditAction,
11592 cx: &mut Context<Self>,
11593 ) {
11594 let Some(breakpoint_store) = &self.breakpoint_store else {
11595 return;
11596 };
11597
11598 let Some(buffer) = self
11599 .buffer
11600 .read(cx)
11601 .buffer_for_anchor(breakpoint_position, cx)
11602 else {
11603 return;
11604 };
11605
11606 breakpoint_store.update(cx, |breakpoint_store, cx| {
11607 breakpoint_store.toggle_breakpoint(
11608 buffer,
11609 BreakpointWithPosition {
11610 position: breakpoint_position.text_anchor,
11611 bp: breakpoint,
11612 },
11613 edit_action,
11614 cx,
11615 );
11616 });
11617
11618 cx.notify();
11619 }
11620
11621 #[cfg(any(test, feature = "test-support"))]
11622 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11623 self.breakpoint_store.clone()
11624 }
11625
11626 pub fn prepare_restore_change(
11627 &self,
11628 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11629 hunk: &MultiBufferDiffHunk,
11630 cx: &mut App,
11631 ) -> Option<()> {
11632 if hunk.is_created_file() {
11633 return None;
11634 }
11635 let buffer = self.buffer.read(cx);
11636 let diff = buffer.diff_for(hunk.buffer_id)?;
11637 let buffer = buffer.buffer(hunk.buffer_id)?;
11638 let buffer = buffer.read(cx);
11639 let original_text = diff
11640 .read(cx)
11641 .base_text(cx)
11642 .as_rope()
11643 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11644 let buffer_snapshot = buffer.snapshot();
11645 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11646 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11647 probe
11648 .0
11649 .start
11650 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11651 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11652 }) {
11653 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11654 Some(())
11655 } else {
11656 None
11657 }
11658 }
11659
11660 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11661 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11662 }
11663
11664 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11665 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11666 }
11667
11668 pub fn rotate_selections_forward(
11669 &mut self,
11670 _: &RotateSelectionsForward,
11671 window: &mut Window,
11672 cx: &mut Context<Self>,
11673 ) {
11674 self.rotate_selections(window, cx, false)
11675 }
11676
11677 pub fn rotate_selections_backward(
11678 &mut self,
11679 _: &RotateSelectionsBackward,
11680 window: &mut Window,
11681 cx: &mut Context<Self>,
11682 ) {
11683 self.rotate_selections(window, cx, true)
11684 }
11685
11686 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11688 let display_snapshot = self.display_snapshot(cx);
11689 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11690
11691 if selections.len() < 2 {
11692 return;
11693 }
11694
11695 let (edits, new_selections) = {
11696 let buffer = self.buffer.read(cx).read(cx);
11697 let has_selections = selections.iter().any(|s| !s.is_empty());
11698 if has_selections {
11699 let mut selected_texts: Vec<String> = selections
11700 .iter()
11701 .map(|selection| {
11702 buffer
11703 .text_for_range(selection.start..selection.end)
11704 .collect()
11705 })
11706 .collect();
11707
11708 if reverse {
11709 selected_texts.rotate_left(1);
11710 } else {
11711 selected_texts.rotate_right(1);
11712 }
11713
11714 let mut offset_delta: i64 = 0;
11715 let mut new_selections = Vec::new();
11716 let edits: Vec<_> = selections
11717 .iter()
11718 .zip(selected_texts.iter())
11719 .map(|(selection, new_text)| {
11720 let old_len = (selection.end.0 - selection.start.0) as i64;
11721 let new_len = new_text.len() as i64;
11722 let adjusted_start =
11723 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11724 let adjusted_end =
11725 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11726
11727 new_selections.push(Selection {
11728 id: selection.id,
11729 start: adjusted_start,
11730 end: adjusted_end,
11731 reversed: selection.reversed,
11732 goal: selection.goal,
11733 });
11734
11735 offset_delta += new_len - old_len;
11736 (selection.start..selection.end, new_text.clone())
11737 })
11738 .collect();
11739 (edits, new_selections)
11740 } else {
11741 let mut all_rows: Vec<u32> = selections
11742 .iter()
11743 .map(|selection| buffer.offset_to_point(selection.start).row)
11744 .collect();
11745 all_rows.sort_unstable();
11746 all_rows.dedup();
11747
11748 if all_rows.len() < 2 {
11749 return;
11750 }
11751
11752 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11753 .iter()
11754 .map(|&row| {
11755 let start = Point::new(row, 0);
11756 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11757 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11758 })
11759 .collect();
11760
11761 let mut line_texts: Vec<String> = line_ranges
11762 .iter()
11763 .map(|range| buffer.text_for_range(range.clone()).collect())
11764 .collect();
11765
11766 if reverse {
11767 line_texts.rotate_left(1);
11768 } else {
11769 line_texts.rotate_right(1);
11770 }
11771
11772 let edits = line_ranges
11773 .iter()
11774 .zip(line_texts.iter())
11775 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11776 .collect();
11777
11778 let num_rows = all_rows.len();
11779 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11780 .iter()
11781 .enumerate()
11782 .map(|(i, &row)| (row, i))
11783 .collect();
11784
11785 // Compute new line start offsets after rotation (handles CRLF)
11786 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11787 let first_line_start = line_ranges[0].start.0;
11788 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11789 for text in line_texts.iter().take(num_rows - 1) {
11790 let prev_start = *new_line_starts.last().unwrap();
11791 new_line_starts.push(prev_start + text.len() + newline_len);
11792 }
11793
11794 let new_selections = selections
11795 .iter()
11796 .map(|selection| {
11797 let point = buffer.offset_to_point(selection.start);
11798 let old_index = row_to_index[&point.row];
11799 let new_index = if reverse {
11800 (old_index + num_rows - 1) % num_rows
11801 } else {
11802 (old_index + 1) % num_rows
11803 };
11804 let new_offset =
11805 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11806 Selection {
11807 id: selection.id,
11808 start: new_offset,
11809 end: new_offset,
11810 reversed: selection.reversed,
11811 goal: selection.goal,
11812 }
11813 })
11814 .collect();
11815
11816 (edits, new_selections)
11817 }
11818 };
11819
11820 self.transact(window, cx, |this, window, cx| {
11821 this.buffer.update(cx, |buffer, cx| {
11822 buffer.edit(edits, None, cx);
11823 });
11824 this.change_selections(Default::default(), window, cx, |s| {
11825 s.select(new_selections);
11826 });
11827 });
11828 }
11829
11830 fn manipulate_lines<M>(
11831 &mut self,
11832 window: &mut Window,
11833 cx: &mut Context<Self>,
11834 mut manipulate: M,
11835 ) where
11836 M: FnMut(&str) -> LineManipulationResult,
11837 {
11838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11839
11840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11841 let buffer = self.buffer.read(cx).snapshot(cx);
11842
11843 let mut edits = Vec::new();
11844
11845 let selections = self.selections.all::<Point>(&display_map);
11846 let mut selections = selections.iter().peekable();
11847 let mut contiguous_row_selections = Vec::new();
11848 let mut new_selections = Vec::new();
11849 let mut added_lines = 0;
11850 let mut removed_lines = 0;
11851
11852 while let Some(selection) = selections.next() {
11853 let (start_row, end_row) = consume_contiguous_rows(
11854 &mut contiguous_row_selections,
11855 selection,
11856 &display_map,
11857 &mut selections,
11858 );
11859
11860 let start_point = Point::new(start_row.0, 0);
11861 let end_point = Point::new(
11862 end_row.previous_row().0,
11863 buffer.line_len(end_row.previous_row()),
11864 );
11865 let text = buffer
11866 .text_for_range(start_point..end_point)
11867 .collect::<String>();
11868
11869 let LineManipulationResult {
11870 new_text,
11871 line_count_before,
11872 line_count_after,
11873 } = manipulate(&text);
11874
11875 edits.push((start_point..end_point, new_text));
11876
11877 // Selections must change based on added and removed line count
11878 let start_row =
11879 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11880 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11881 new_selections.push(Selection {
11882 id: selection.id,
11883 start: start_row,
11884 end: end_row,
11885 goal: SelectionGoal::None,
11886 reversed: selection.reversed,
11887 });
11888
11889 if line_count_after > line_count_before {
11890 added_lines += line_count_after - line_count_before;
11891 } else if line_count_before > line_count_after {
11892 removed_lines += line_count_before - line_count_after;
11893 }
11894 }
11895
11896 self.transact(window, cx, |this, window, cx| {
11897 let buffer = this.buffer.update(cx, |buffer, cx| {
11898 buffer.edit(edits, None, cx);
11899 buffer.snapshot(cx)
11900 });
11901
11902 // Recalculate offsets on newly edited buffer
11903 let new_selections = new_selections
11904 .iter()
11905 .map(|s| {
11906 let start_point = Point::new(s.start.0, 0);
11907 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11908 Selection {
11909 id: s.id,
11910 start: buffer.point_to_offset(start_point),
11911 end: buffer.point_to_offset(end_point),
11912 goal: s.goal,
11913 reversed: s.reversed,
11914 }
11915 })
11916 .collect();
11917
11918 this.change_selections(Default::default(), window, cx, |s| {
11919 s.select(new_selections);
11920 });
11921
11922 this.request_autoscroll(Autoscroll::fit(), cx);
11923 });
11924 }
11925
11926 fn manipulate_immutable_lines<Fn>(
11927 &mut self,
11928 window: &mut Window,
11929 cx: &mut Context<Self>,
11930 mut callback: Fn,
11931 ) where
11932 Fn: FnMut(&mut Vec<&str>),
11933 {
11934 self.manipulate_lines(window, cx, |text| {
11935 let mut lines: Vec<&str> = text.split('\n').collect();
11936 let line_count_before = lines.len();
11937
11938 callback(&mut lines);
11939
11940 LineManipulationResult {
11941 new_text: lines.join("\n"),
11942 line_count_before,
11943 line_count_after: lines.len(),
11944 }
11945 });
11946 }
11947
11948 fn manipulate_mutable_lines<Fn>(
11949 &mut self,
11950 window: &mut Window,
11951 cx: &mut Context<Self>,
11952 mut callback: Fn,
11953 ) where
11954 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11955 {
11956 self.manipulate_lines(window, cx, |text| {
11957 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11958 let line_count_before = lines.len();
11959
11960 callback(&mut lines);
11961
11962 LineManipulationResult {
11963 new_text: lines.join("\n"),
11964 line_count_before,
11965 line_count_after: lines.len(),
11966 }
11967 });
11968 }
11969
11970 pub fn convert_indentation_to_spaces(
11971 &mut self,
11972 _: &ConvertIndentationToSpaces,
11973 window: &mut Window,
11974 cx: &mut Context<Self>,
11975 ) {
11976 let settings = self.buffer.read(cx).language_settings(cx);
11977 let tab_size = settings.tab_size.get() as usize;
11978
11979 self.manipulate_mutable_lines(window, cx, |lines| {
11980 // Allocates a reasonably sized scratch buffer once for the whole loop
11981 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11982 // Avoids recomputing spaces that could be inserted many times
11983 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11984 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11985 .collect();
11986
11987 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11988 let mut chars = line.as_ref().chars();
11989 let mut col = 0;
11990 let mut changed = false;
11991
11992 for ch in chars.by_ref() {
11993 match ch {
11994 ' ' => {
11995 reindented_line.push(' ');
11996 col += 1;
11997 }
11998 '\t' => {
11999 // \t are converted to spaces depending on the current column
12000 let spaces_len = tab_size - (col % tab_size);
12001 reindented_line.extend(&space_cache[spaces_len - 1]);
12002 col += spaces_len;
12003 changed = true;
12004 }
12005 _ => {
12006 // If we dont append before break, the character is consumed
12007 reindented_line.push(ch);
12008 break;
12009 }
12010 }
12011 }
12012
12013 if !changed {
12014 reindented_line.clear();
12015 continue;
12016 }
12017 // Append the rest of the line and replace old reference with new one
12018 reindented_line.extend(chars);
12019 *line = Cow::Owned(reindented_line.clone());
12020 reindented_line.clear();
12021 }
12022 });
12023 }
12024
12025 pub fn convert_indentation_to_tabs(
12026 &mut self,
12027 _: &ConvertIndentationToTabs,
12028 window: &mut Window,
12029 cx: &mut Context<Self>,
12030 ) {
12031 let settings = self.buffer.read(cx).language_settings(cx);
12032 let tab_size = settings.tab_size.get() as usize;
12033
12034 self.manipulate_mutable_lines(window, cx, |lines| {
12035 // Allocates a reasonably sized buffer once for the whole loop
12036 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12037 // Avoids recomputing spaces that could be inserted many times
12038 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12039 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12040 .collect();
12041
12042 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12043 let mut chars = line.chars();
12044 let mut spaces_count = 0;
12045 let mut first_non_indent_char = None;
12046 let mut changed = false;
12047
12048 for ch in chars.by_ref() {
12049 match ch {
12050 ' ' => {
12051 // Keep track of spaces. Append \t when we reach tab_size
12052 spaces_count += 1;
12053 changed = true;
12054 if spaces_count == tab_size {
12055 reindented_line.push('\t');
12056 spaces_count = 0;
12057 }
12058 }
12059 '\t' => {
12060 reindented_line.push('\t');
12061 spaces_count = 0;
12062 }
12063 _ => {
12064 // Dont append it yet, we might have remaining spaces
12065 first_non_indent_char = Some(ch);
12066 break;
12067 }
12068 }
12069 }
12070
12071 if !changed {
12072 reindented_line.clear();
12073 continue;
12074 }
12075 // Remaining spaces that didn't make a full tab stop
12076 if spaces_count > 0 {
12077 reindented_line.extend(&space_cache[spaces_count - 1]);
12078 }
12079 // If we consume an extra character that was not indentation, add it back
12080 if let Some(extra_char) = first_non_indent_char {
12081 reindented_line.push(extra_char);
12082 }
12083 // Append the rest of the line and replace old reference with new one
12084 reindented_line.extend(chars);
12085 *line = Cow::Owned(reindented_line.clone());
12086 reindented_line.clear();
12087 }
12088 });
12089 }
12090
12091 pub fn convert_to_upper_case(
12092 &mut self,
12093 _: &ConvertToUpperCase,
12094 window: &mut Window,
12095 cx: &mut Context<Self>,
12096 ) {
12097 self.manipulate_text(window, cx, |text| text.to_uppercase())
12098 }
12099
12100 pub fn convert_to_lower_case(
12101 &mut self,
12102 _: &ConvertToLowerCase,
12103 window: &mut Window,
12104 cx: &mut Context<Self>,
12105 ) {
12106 self.manipulate_text(window, cx, |text| text.to_lowercase())
12107 }
12108
12109 pub fn convert_to_title_case(
12110 &mut self,
12111 _: &ConvertToTitleCase,
12112 window: &mut Window,
12113 cx: &mut Context<Self>,
12114 ) {
12115 self.manipulate_text(window, cx, |text| {
12116 text.split('\n')
12117 .map(|line| line.to_case(Case::Title))
12118 .join("\n")
12119 })
12120 }
12121
12122 pub fn convert_to_snake_case(
12123 &mut self,
12124 _: &ConvertToSnakeCase,
12125 window: &mut Window,
12126 cx: &mut Context<Self>,
12127 ) {
12128 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12129 }
12130
12131 pub fn convert_to_kebab_case(
12132 &mut self,
12133 _: &ConvertToKebabCase,
12134 window: &mut Window,
12135 cx: &mut Context<Self>,
12136 ) {
12137 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12138 }
12139
12140 pub fn convert_to_upper_camel_case(
12141 &mut self,
12142 _: &ConvertToUpperCamelCase,
12143 window: &mut Window,
12144 cx: &mut Context<Self>,
12145 ) {
12146 self.manipulate_text(window, cx, |text| {
12147 text.split('\n')
12148 .map(|line| line.to_case(Case::UpperCamel))
12149 .join("\n")
12150 })
12151 }
12152
12153 pub fn convert_to_lower_camel_case(
12154 &mut self,
12155 _: &ConvertToLowerCamelCase,
12156 window: &mut Window,
12157 cx: &mut Context<Self>,
12158 ) {
12159 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12160 }
12161
12162 pub fn convert_to_opposite_case(
12163 &mut self,
12164 _: &ConvertToOppositeCase,
12165 window: &mut Window,
12166 cx: &mut Context<Self>,
12167 ) {
12168 self.manipulate_text(window, cx, |text| {
12169 text.chars()
12170 .fold(String::with_capacity(text.len()), |mut t, c| {
12171 if c.is_uppercase() {
12172 t.extend(c.to_lowercase());
12173 } else {
12174 t.extend(c.to_uppercase());
12175 }
12176 t
12177 })
12178 })
12179 }
12180
12181 pub fn convert_to_sentence_case(
12182 &mut self,
12183 _: &ConvertToSentenceCase,
12184 window: &mut Window,
12185 cx: &mut Context<Self>,
12186 ) {
12187 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12188 }
12189
12190 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12191 self.manipulate_text(window, cx, |text| {
12192 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12193 if has_upper_case_characters {
12194 text.to_lowercase()
12195 } else {
12196 text.to_uppercase()
12197 }
12198 })
12199 }
12200
12201 pub fn convert_to_rot13(
12202 &mut self,
12203 _: &ConvertToRot13,
12204 window: &mut Window,
12205 cx: &mut Context<Self>,
12206 ) {
12207 self.manipulate_text(window, cx, |text| {
12208 text.chars()
12209 .map(|c| match c {
12210 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12211 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12212 _ => c,
12213 })
12214 .collect()
12215 })
12216 }
12217
12218 pub fn convert_to_rot47(
12219 &mut self,
12220 _: &ConvertToRot47,
12221 window: &mut Window,
12222 cx: &mut Context<Self>,
12223 ) {
12224 self.manipulate_text(window, cx, |text| {
12225 text.chars()
12226 .map(|c| {
12227 let code_point = c as u32;
12228 if code_point >= 33 && code_point <= 126 {
12229 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12230 }
12231 c
12232 })
12233 .collect()
12234 })
12235 }
12236
12237 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12238 where
12239 Fn: FnMut(&str) -> String,
12240 {
12241 let buffer = self.buffer.read(cx).snapshot(cx);
12242
12243 let mut new_selections = Vec::new();
12244 let mut edits = Vec::new();
12245 let mut selection_adjustment = 0isize;
12246
12247 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12248 let selection_is_empty = selection.is_empty();
12249
12250 let (start, end) = if selection_is_empty {
12251 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12252 (word_range.start, word_range.end)
12253 } else {
12254 (
12255 buffer.point_to_offset(selection.start),
12256 buffer.point_to_offset(selection.end),
12257 )
12258 };
12259
12260 let text = buffer.text_for_range(start..end).collect::<String>();
12261 let old_length = text.len() as isize;
12262 let text = callback(&text);
12263
12264 new_selections.push(Selection {
12265 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12266 end: MultiBufferOffset(
12267 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12268 ),
12269 goal: SelectionGoal::None,
12270 id: selection.id,
12271 reversed: selection.reversed,
12272 });
12273
12274 selection_adjustment += old_length - text.len() as isize;
12275
12276 edits.push((start..end, text));
12277 }
12278
12279 self.transact(window, cx, |this, window, cx| {
12280 this.buffer.update(cx, |buffer, cx| {
12281 buffer.edit(edits, None, cx);
12282 });
12283
12284 this.change_selections(Default::default(), window, cx, |s| {
12285 s.select(new_selections);
12286 });
12287
12288 this.request_autoscroll(Autoscroll::fit(), cx);
12289 });
12290 }
12291
12292 pub fn move_selection_on_drop(
12293 &mut self,
12294 selection: &Selection<Anchor>,
12295 target: DisplayPoint,
12296 is_cut: bool,
12297 window: &mut Window,
12298 cx: &mut Context<Self>,
12299 ) {
12300 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12301 let buffer = display_map.buffer_snapshot();
12302 let mut edits = Vec::new();
12303 let insert_point = display_map
12304 .clip_point(target, Bias::Left)
12305 .to_point(&display_map);
12306 let text = buffer
12307 .text_for_range(selection.start..selection.end)
12308 .collect::<String>();
12309 if is_cut {
12310 edits.push(((selection.start..selection.end), String::new()));
12311 }
12312 let insert_anchor = buffer.anchor_before(insert_point);
12313 edits.push(((insert_anchor..insert_anchor), text));
12314 let last_edit_start = insert_anchor.bias_left(buffer);
12315 let last_edit_end = insert_anchor.bias_right(buffer);
12316 self.transact(window, cx, |this, window, cx| {
12317 this.buffer.update(cx, |buffer, cx| {
12318 buffer.edit(edits, None, cx);
12319 });
12320 this.change_selections(Default::default(), window, cx, |s| {
12321 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12322 });
12323 });
12324 }
12325
12326 pub fn clear_selection_drag_state(&mut self) {
12327 self.selection_drag_state = SelectionDragState::None;
12328 }
12329
12330 pub fn duplicate(
12331 &mut self,
12332 upwards: bool,
12333 whole_lines: bool,
12334 window: &mut Window,
12335 cx: &mut Context<Self>,
12336 ) {
12337 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12338
12339 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12340 let buffer = display_map.buffer_snapshot();
12341 let selections = self.selections.all::<Point>(&display_map);
12342
12343 let mut edits = Vec::new();
12344 let mut selections_iter = selections.iter().peekable();
12345 while let Some(selection) = selections_iter.next() {
12346 let mut rows = selection.spanned_rows(false, &display_map);
12347 // duplicate line-wise
12348 if whole_lines || selection.start == selection.end {
12349 // Avoid duplicating the same lines twice.
12350 while let Some(next_selection) = selections_iter.peek() {
12351 let next_rows = next_selection.spanned_rows(false, &display_map);
12352 if next_rows.start < rows.end {
12353 rows.end = next_rows.end;
12354 selections_iter.next().unwrap();
12355 } else {
12356 break;
12357 }
12358 }
12359
12360 // Copy the text from the selected row region and splice it either at the start
12361 // or end of the region.
12362 let start = Point::new(rows.start.0, 0);
12363 let end = Point::new(
12364 rows.end.previous_row().0,
12365 buffer.line_len(rows.end.previous_row()),
12366 );
12367
12368 let mut text = buffer.text_for_range(start..end).collect::<String>();
12369
12370 let insert_location = if upwards {
12371 // When duplicating upward, we need to insert before the current line.
12372 // If we're on the last line and it doesn't end with a newline,
12373 // we need to add a newline before the duplicated content.
12374 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12375 && buffer.max_point().column > 0
12376 && !text.ends_with('\n');
12377
12378 if needs_leading_newline {
12379 text.insert(0, '\n');
12380 end
12381 } else {
12382 text.push('\n');
12383 Point::new(rows.start.0, 0)
12384 }
12385 } else {
12386 text.push('\n');
12387 start
12388 };
12389 edits.push((insert_location..insert_location, text));
12390 } else {
12391 // duplicate character-wise
12392 let start = selection.start;
12393 let end = selection.end;
12394 let text = buffer.text_for_range(start..end).collect::<String>();
12395 edits.push((selection.end..selection.end, text));
12396 }
12397 }
12398
12399 self.transact(window, cx, |this, window, cx| {
12400 this.buffer.update(cx, |buffer, cx| {
12401 buffer.edit(edits, None, cx);
12402 });
12403
12404 // When duplicating upward with whole lines, move the cursor to the duplicated line
12405 if upwards && whole_lines {
12406 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12407
12408 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12409 let mut new_ranges = Vec::new();
12410 let selections = s.all::<Point>(&display_map);
12411 let mut selections_iter = selections.iter().peekable();
12412
12413 while let Some(first_selection) = selections_iter.next() {
12414 // Group contiguous selections together to find the total row span
12415 let mut group_selections = vec![first_selection];
12416 let mut rows = first_selection.spanned_rows(false, &display_map);
12417
12418 while let Some(next_selection) = selections_iter.peek() {
12419 let next_rows = next_selection.spanned_rows(false, &display_map);
12420 if next_rows.start < rows.end {
12421 rows.end = next_rows.end;
12422 group_selections.push(selections_iter.next().unwrap());
12423 } else {
12424 break;
12425 }
12426 }
12427
12428 let row_count = rows.end.0 - rows.start.0;
12429
12430 // Move all selections in this group up by the total number of duplicated rows
12431 for selection in group_selections {
12432 let new_start = Point::new(
12433 selection.start.row.saturating_sub(row_count),
12434 selection.start.column,
12435 );
12436
12437 let new_end = Point::new(
12438 selection.end.row.saturating_sub(row_count),
12439 selection.end.column,
12440 );
12441
12442 new_ranges.push(new_start..new_end);
12443 }
12444 }
12445
12446 s.select_ranges(new_ranges);
12447 });
12448 }
12449
12450 this.request_autoscroll(Autoscroll::fit(), cx);
12451 });
12452 }
12453
12454 pub fn duplicate_line_up(
12455 &mut self,
12456 _: &DuplicateLineUp,
12457 window: &mut Window,
12458 cx: &mut Context<Self>,
12459 ) {
12460 self.duplicate(true, true, window, cx);
12461 }
12462
12463 pub fn duplicate_line_down(
12464 &mut self,
12465 _: &DuplicateLineDown,
12466 window: &mut Window,
12467 cx: &mut Context<Self>,
12468 ) {
12469 self.duplicate(false, true, window, cx);
12470 }
12471
12472 pub fn duplicate_selection(
12473 &mut self,
12474 _: &DuplicateSelection,
12475 window: &mut Window,
12476 cx: &mut Context<Self>,
12477 ) {
12478 self.duplicate(false, false, window, cx);
12479 }
12480
12481 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12482 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12483 if self.mode.is_single_line() {
12484 cx.propagate();
12485 return;
12486 }
12487
12488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12489 let buffer = self.buffer.read(cx).snapshot(cx);
12490
12491 let mut edits = Vec::new();
12492 let mut unfold_ranges = Vec::new();
12493 let mut refold_creases = Vec::new();
12494
12495 let selections = self.selections.all::<Point>(&display_map);
12496 let mut selections = selections.iter().peekable();
12497 let mut contiguous_row_selections = Vec::new();
12498 let mut new_selections = Vec::new();
12499
12500 while let Some(selection) = selections.next() {
12501 // Find all the selections that span a contiguous row range
12502 let (start_row, end_row) = consume_contiguous_rows(
12503 &mut contiguous_row_selections,
12504 selection,
12505 &display_map,
12506 &mut selections,
12507 );
12508
12509 // Move the text spanned by the row range to be before the line preceding the row range
12510 if start_row.0 > 0 {
12511 let range_to_move = Point::new(
12512 start_row.previous_row().0,
12513 buffer.line_len(start_row.previous_row()),
12514 )
12515 ..Point::new(
12516 end_row.previous_row().0,
12517 buffer.line_len(end_row.previous_row()),
12518 );
12519 let insertion_point = display_map
12520 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12521 .0;
12522
12523 // Don't move lines across excerpts
12524 if buffer
12525 .excerpt_containing(insertion_point..range_to_move.end)
12526 .is_some()
12527 {
12528 let text = buffer
12529 .text_for_range(range_to_move.clone())
12530 .flat_map(|s| s.chars())
12531 .skip(1)
12532 .chain(['\n'])
12533 .collect::<String>();
12534
12535 edits.push((
12536 buffer.anchor_after(range_to_move.start)
12537 ..buffer.anchor_before(range_to_move.end),
12538 String::new(),
12539 ));
12540 let insertion_anchor = buffer.anchor_after(insertion_point);
12541 edits.push((insertion_anchor..insertion_anchor, text));
12542
12543 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12544
12545 // Move selections up
12546 new_selections.extend(contiguous_row_selections.drain(..).map(
12547 |mut selection| {
12548 selection.start.row -= row_delta;
12549 selection.end.row -= row_delta;
12550 selection
12551 },
12552 ));
12553
12554 // Move folds up
12555 unfold_ranges.push(range_to_move.clone());
12556 for fold in display_map.folds_in_range(
12557 buffer.anchor_before(range_to_move.start)
12558 ..buffer.anchor_after(range_to_move.end),
12559 ) {
12560 let mut start = fold.range.start.to_point(&buffer);
12561 let mut end = fold.range.end.to_point(&buffer);
12562 start.row -= row_delta;
12563 end.row -= row_delta;
12564 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12565 }
12566 }
12567 }
12568
12569 // If we didn't move line(s), preserve the existing selections
12570 new_selections.append(&mut contiguous_row_selections);
12571 }
12572
12573 self.transact(window, cx, |this, window, cx| {
12574 this.unfold_ranges(&unfold_ranges, true, true, cx);
12575 this.buffer.update(cx, |buffer, cx| {
12576 for (range, text) in edits {
12577 buffer.edit([(range, text)], None, cx);
12578 }
12579 });
12580 this.fold_creases(refold_creases, true, window, cx);
12581 this.change_selections(Default::default(), window, cx, |s| {
12582 s.select(new_selections);
12583 })
12584 });
12585 }
12586
12587 pub fn move_line_down(
12588 &mut self,
12589 _: &MoveLineDown,
12590 window: &mut Window,
12591 cx: &mut Context<Self>,
12592 ) {
12593 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12594 if self.mode.is_single_line() {
12595 cx.propagate();
12596 return;
12597 }
12598
12599 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12600 let buffer = self.buffer.read(cx).snapshot(cx);
12601
12602 let mut edits = Vec::new();
12603 let mut unfold_ranges = Vec::new();
12604 let mut refold_creases = Vec::new();
12605
12606 let selections = self.selections.all::<Point>(&display_map);
12607 let mut selections = selections.iter().peekable();
12608 let mut contiguous_row_selections = Vec::new();
12609 let mut new_selections = Vec::new();
12610
12611 while let Some(selection) = selections.next() {
12612 // Find all the selections that span a contiguous row range
12613 let (start_row, end_row) = consume_contiguous_rows(
12614 &mut contiguous_row_selections,
12615 selection,
12616 &display_map,
12617 &mut selections,
12618 );
12619
12620 // Move the text spanned by the row range to be after the last line of the row range
12621 if end_row.0 <= buffer.max_point().row {
12622 let range_to_move =
12623 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12624 let insertion_point = display_map
12625 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12626 .0;
12627
12628 // Don't move lines across excerpt boundaries
12629 if buffer
12630 .excerpt_containing(range_to_move.start..insertion_point)
12631 .is_some()
12632 {
12633 let mut text = String::from("\n");
12634 text.extend(buffer.text_for_range(range_to_move.clone()));
12635 text.pop(); // Drop trailing newline
12636 edits.push((
12637 buffer.anchor_after(range_to_move.start)
12638 ..buffer.anchor_before(range_to_move.end),
12639 String::new(),
12640 ));
12641 let insertion_anchor = buffer.anchor_after(insertion_point);
12642 edits.push((insertion_anchor..insertion_anchor, text));
12643
12644 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12645
12646 // Move selections down
12647 new_selections.extend(contiguous_row_selections.drain(..).map(
12648 |mut selection| {
12649 selection.start.row += row_delta;
12650 selection.end.row += row_delta;
12651 selection
12652 },
12653 ));
12654
12655 // Move folds down
12656 unfold_ranges.push(range_to_move.clone());
12657 for fold in display_map.folds_in_range(
12658 buffer.anchor_before(range_to_move.start)
12659 ..buffer.anchor_after(range_to_move.end),
12660 ) {
12661 let mut start = fold.range.start.to_point(&buffer);
12662 let mut end = fold.range.end.to_point(&buffer);
12663 start.row += row_delta;
12664 end.row += row_delta;
12665 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12666 }
12667 }
12668 }
12669
12670 // If we didn't move line(s), preserve the existing selections
12671 new_selections.append(&mut contiguous_row_selections);
12672 }
12673
12674 self.transact(window, cx, |this, window, cx| {
12675 this.unfold_ranges(&unfold_ranges, true, true, cx);
12676 this.buffer.update(cx, |buffer, cx| {
12677 for (range, text) in edits {
12678 buffer.edit([(range, text)], None, cx);
12679 }
12680 });
12681 this.fold_creases(refold_creases, true, window, cx);
12682 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12683 });
12684 }
12685
12686 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12688 let text_layout_details = &self.text_layout_details(window);
12689 self.transact(window, cx, |this, window, cx| {
12690 let edits = this.change_selections(Default::default(), window, cx, |s| {
12691 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12692 s.move_with(|display_map, selection| {
12693 if !selection.is_empty() {
12694 return;
12695 }
12696
12697 let mut head = selection.head();
12698 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12699 if head.column() == display_map.line_len(head.row()) {
12700 transpose_offset = display_map
12701 .buffer_snapshot()
12702 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12703 }
12704
12705 if transpose_offset == MultiBufferOffset(0) {
12706 return;
12707 }
12708
12709 *head.column_mut() += 1;
12710 head = display_map.clip_point(head, Bias::Right);
12711 let goal = SelectionGoal::HorizontalPosition(
12712 display_map
12713 .x_for_display_point(head, text_layout_details)
12714 .into(),
12715 );
12716 selection.collapse_to(head, goal);
12717
12718 let transpose_start = display_map
12719 .buffer_snapshot()
12720 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12721 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12722 let transpose_end = display_map
12723 .buffer_snapshot()
12724 .clip_offset(transpose_offset + 1usize, Bias::Right);
12725 if let Some(ch) = display_map
12726 .buffer_snapshot()
12727 .chars_at(transpose_start)
12728 .next()
12729 {
12730 edits.push((transpose_start..transpose_offset, String::new()));
12731 edits.push((transpose_end..transpose_end, ch.to_string()));
12732 }
12733 }
12734 });
12735 edits
12736 });
12737 this.buffer
12738 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12739 let selections = this
12740 .selections
12741 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12742 this.change_selections(Default::default(), window, cx, |s| {
12743 s.select(selections);
12744 });
12745 });
12746 }
12747
12748 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12750 if self.mode.is_single_line() {
12751 cx.propagate();
12752 return;
12753 }
12754
12755 self.rewrap_impl(RewrapOptions::default(), cx)
12756 }
12757
12758 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12759 let buffer = self.buffer.read(cx).snapshot(cx);
12760 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12761
12762 #[derive(Clone, Debug, PartialEq)]
12763 enum CommentFormat {
12764 /// single line comment, with prefix for line
12765 Line(String),
12766 /// single line within a block comment, with prefix for line
12767 BlockLine(String),
12768 /// a single line of a block comment that includes the initial delimiter
12769 BlockCommentWithStart(BlockCommentConfig),
12770 /// a single line of a block comment that includes the ending delimiter
12771 BlockCommentWithEnd(BlockCommentConfig),
12772 }
12773
12774 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12775 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12776 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12777 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12778 .peekable();
12779
12780 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12781 row
12782 } else {
12783 return Vec::new();
12784 };
12785
12786 let language_settings = buffer.language_settings_at(selection.head(), cx);
12787 let language_scope = buffer.language_scope_at(selection.head());
12788
12789 let indent_and_prefix_for_row =
12790 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12791 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12792 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12793 &language_scope
12794 {
12795 let indent_end = Point::new(row, indent.len);
12796 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12797 let line_text_after_indent = buffer
12798 .text_for_range(indent_end..line_end)
12799 .collect::<String>();
12800
12801 let is_within_comment_override = buffer
12802 .language_scope_at(indent_end)
12803 .is_some_and(|scope| scope.override_name() == Some("comment"));
12804 let comment_delimiters = if is_within_comment_override {
12805 // we are within a comment syntax node, but we don't
12806 // yet know what kind of comment: block, doc or line
12807 match (
12808 language_scope.documentation_comment(),
12809 language_scope.block_comment(),
12810 ) {
12811 (Some(config), _) | (_, Some(config))
12812 if buffer.contains_str_at(indent_end, &config.start) =>
12813 {
12814 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12815 }
12816 (Some(config), _) | (_, Some(config))
12817 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12818 {
12819 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12820 }
12821 (Some(config), _) | (_, Some(config))
12822 if buffer.contains_str_at(indent_end, &config.prefix) =>
12823 {
12824 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12825 }
12826 (_, _) => language_scope
12827 .line_comment_prefixes()
12828 .iter()
12829 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12830 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12831 }
12832 } else {
12833 // we not in an overridden comment node, but we may
12834 // be within a non-overridden line comment node
12835 language_scope
12836 .line_comment_prefixes()
12837 .iter()
12838 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12839 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12840 };
12841
12842 let rewrap_prefix = language_scope
12843 .rewrap_prefixes()
12844 .iter()
12845 .find_map(|prefix_regex| {
12846 prefix_regex.find(&line_text_after_indent).map(|mat| {
12847 if mat.start() == 0 {
12848 Some(mat.as_str().to_string())
12849 } else {
12850 None
12851 }
12852 })
12853 })
12854 .flatten();
12855 (comment_delimiters, rewrap_prefix)
12856 } else {
12857 (None, None)
12858 };
12859 (indent, comment_prefix, rewrap_prefix)
12860 };
12861
12862 let mut ranges = Vec::new();
12863 let from_empty_selection = selection.is_empty();
12864
12865 let mut current_range_start = first_row;
12866 let mut prev_row = first_row;
12867 let (
12868 mut current_range_indent,
12869 mut current_range_comment_delimiters,
12870 mut current_range_rewrap_prefix,
12871 ) = indent_and_prefix_for_row(first_row);
12872
12873 for row in non_blank_rows_iter.skip(1) {
12874 let has_paragraph_break = row > prev_row + 1;
12875
12876 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12877 indent_and_prefix_for_row(row);
12878
12879 let has_indent_change = row_indent != current_range_indent;
12880 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12881
12882 let has_boundary_change = has_comment_change
12883 || row_rewrap_prefix.is_some()
12884 || (has_indent_change && current_range_comment_delimiters.is_some());
12885
12886 if has_paragraph_break || has_boundary_change {
12887 ranges.push((
12888 language_settings.clone(),
12889 Point::new(current_range_start, 0)
12890 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12891 current_range_indent,
12892 current_range_comment_delimiters.clone(),
12893 current_range_rewrap_prefix.clone(),
12894 from_empty_selection,
12895 ));
12896 current_range_start = row;
12897 current_range_indent = row_indent;
12898 current_range_comment_delimiters = row_comment_delimiters;
12899 current_range_rewrap_prefix = row_rewrap_prefix;
12900 }
12901 prev_row = row;
12902 }
12903
12904 ranges.push((
12905 language_settings.clone(),
12906 Point::new(current_range_start, 0)
12907 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12908 current_range_indent,
12909 current_range_comment_delimiters,
12910 current_range_rewrap_prefix,
12911 from_empty_selection,
12912 ));
12913
12914 ranges
12915 });
12916
12917 let mut edits = Vec::new();
12918 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12919
12920 for (
12921 language_settings,
12922 wrap_range,
12923 mut indent_size,
12924 comment_prefix,
12925 rewrap_prefix,
12926 from_empty_selection,
12927 ) in wrap_ranges
12928 {
12929 let mut start_row = wrap_range.start.row;
12930 let mut end_row = wrap_range.end.row;
12931
12932 // Skip selections that overlap with a range that has already been rewrapped.
12933 let selection_range = start_row..end_row;
12934 if rewrapped_row_ranges
12935 .iter()
12936 .any(|range| range.overlaps(&selection_range))
12937 {
12938 continue;
12939 }
12940
12941 let tab_size = language_settings.tab_size;
12942
12943 let (line_prefix, inside_comment) = match &comment_prefix {
12944 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12945 (Some(prefix.as_str()), true)
12946 }
12947 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12948 (Some(prefix.as_ref()), true)
12949 }
12950 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12951 start: _,
12952 end: _,
12953 prefix,
12954 tab_size,
12955 })) => {
12956 indent_size.len += tab_size;
12957 (Some(prefix.as_ref()), true)
12958 }
12959 None => (None, false),
12960 };
12961 let indent_prefix = indent_size.chars().collect::<String>();
12962 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12963
12964 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12965 RewrapBehavior::InComments => inside_comment,
12966 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12967 RewrapBehavior::Anywhere => true,
12968 };
12969
12970 let should_rewrap = options.override_language_settings
12971 || allow_rewrap_based_on_language
12972 || self.hard_wrap.is_some();
12973 if !should_rewrap {
12974 continue;
12975 }
12976
12977 if from_empty_selection {
12978 'expand_upwards: while start_row > 0 {
12979 let prev_row = start_row - 1;
12980 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12981 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12982 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12983 {
12984 start_row = prev_row;
12985 } else {
12986 break 'expand_upwards;
12987 }
12988 }
12989
12990 'expand_downwards: while end_row < buffer.max_point().row {
12991 let next_row = end_row + 1;
12992 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12993 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12994 && !buffer.is_line_blank(MultiBufferRow(next_row))
12995 {
12996 end_row = next_row;
12997 } else {
12998 break 'expand_downwards;
12999 }
13000 }
13001 }
13002
13003 let start = Point::new(start_row, 0);
13004 let start_offset = ToOffset::to_offset(&start, &buffer);
13005 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13006 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13007 let mut first_line_delimiter = None;
13008 let mut last_line_delimiter = None;
13009 let Some(lines_without_prefixes) = selection_text
13010 .lines()
13011 .enumerate()
13012 .map(|(ix, line)| {
13013 let line_trimmed = line.trim_start();
13014 if rewrap_prefix.is_some() && ix > 0 {
13015 Ok(line_trimmed)
13016 } else if let Some(
13017 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13018 start,
13019 prefix,
13020 end,
13021 tab_size,
13022 })
13023 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13024 start,
13025 prefix,
13026 end,
13027 tab_size,
13028 }),
13029 ) = &comment_prefix
13030 {
13031 let line_trimmed = line_trimmed
13032 .strip_prefix(start.as_ref())
13033 .map(|s| {
13034 let mut indent_size = indent_size;
13035 indent_size.len -= tab_size;
13036 let indent_prefix: String = indent_size.chars().collect();
13037 first_line_delimiter = Some((indent_prefix, start));
13038 s.trim_start()
13039 })
13040 .unwrap_or(line_trimmed);
13041 let line_trimmed = line_trimmed
13042 .strip_suffix(end.as_ref())
13043 .map(|s| {
13044 last_line_delimiter = Some(end);
13045 s.trim_end()
13046 })
13047 .unwrap_or(line_trimmed);
13048 let line_trimmed = line_trimmed
13049 .strip_prefix(prefix.as_ref())
13050 .unwrap_or(line_trimmed);
13051 Ok(line_trimmed)
13052 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13053 line_trimmed.strip_prefix(prefix).with_context(|| {
13054 format!("line did not start with prefix {prefix:?}: {line:?}")
13055 })
13056 } else {
13057 line_trimmed
13058 .strip_prefix(&line_prefix.trim_start())
13059 .with_context(|| {
13060 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13061 })
13062 }
13063 })
13064 .collect::<Result<Vec<_>, _>>()
13065 .log_err()
13066 else {
13067 continue;
13068 };
13069
13070 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13071 buffer
13072 .language_settings_at(Point::new(start_row, 0), cx)
13073 .preferred_line_length as usize
13074 });
13075
13076 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13077 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13078 } else {
13079 line_prefix.clone()
13080 };
13081
13082 let wrapped_text = {
13083 let mut wrapped_text = wrap_with_prefix(
13084 line_prefix,
13085 subsequent_lines_prefix,
13086 lines_without_prefixes.join("\n"),
13087 wrap_column,
13088 tab_size,
13089 options.preserve_existing_whitespace,
13090 );
13091
13092 if let Some((indent, delimiter)) = first_line_delimiter {
13093 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13094 }
13095 if let Some(last_line) = last_line_delimiter {
13096 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13097 }
13098
13099 wrapped_text
13100 };
13101
13102 // TODO: should always use char-based diff while still supporting cursor behavior that
13103 // matches vim.
13104 let mut diff_options = DiffOptions::default();
13105 if options.override_language_settings {
13106 diff_options.max_word_diff_len = 0;
13107 diff_options.max_word_diff_line_count = 0;
13108 } else {
13109 diff_options.max_word_diff_len = usize::MAX;
13110 diff_options.max_word_diff_line_count = usize::MAX;
13111 }
13112
13113 for (old_range, new_text) in
13114 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13115 {
13116 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13117 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13118 edits.push((edit_start..edit_end, new_text));
13119 }
13120
13121 rewrapped_row_ranges.push(start_row..=end_row);
13122 }
13123
13124 self.buffer
13125 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13126 }
13127
13128 pub fn cut_common(
13129 &mut self,
13130 cut_no_selection_line: bool,
13131 window: &mut Window,
13132 cx: &mut Context<Self>,
13133 ) -> ClipboardItem {
13134 let mut text = String::new();
13135 let buffer = self.buffer.read(cx).snapshot(cx);
13136 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13137 let mut clipboard_selections = Vec::with_capacity(selections.len());
13138 {
13139 let max_point = buffer.max_point();
13140 let mut is_first = true;
13141 let mut prev_selection_was_entire_line = false;
13142 for selection in &mut selections {
13143 let is_entire_line =
13144 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13145 if is_entire_line {
13146 selection.start = Point::new(selection.start.row, 0);
13147 if !selection.is_empty() && selection.end.column == 0 {
13148 selection.end = cmp::min(max_point, selection.end);
13149 } else {
13150 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13151 }
13152 selection.goal = SelectionGoal::None;
13153 }
13154 if is_first {
13155 is_first = false;
13156 } else if !prev_selection_was_entire_line {
13157 text += "\n";
13158 }
13159 prev_selection_was_entire_line = is_entire_line;
13160 let mut len = 0;
13161 for chunk in buffer.text_for_range(selection.start..selection.end) {
13162 text.push_str(chunk);
13163 len += chunk.len();
13164 }
13165
13166 clipboard_selections.push(ClipboardSelection::for_buffer(
13167 len,
13168 is_entire_line,
13169 selection.range(),
13170 &buffer,
13171 self.project.as_ref(),
13172 cx,
13173 ));
13174 }
13175 }
13176
13177 self.transact(window, cx, |this, window, cx| {
13178 this.change_selections(Default::default(), window, cx, |s| {
13179 s.select(selections);
13180 });
13181 this.insert("", window, cx);
13182 });
13183 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13184 }
13185
13186 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13187 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13188 let item = self.cut_common(true, window, cx);
13189 cx.write_to_clipboard(item);
13190 }
13191
13192 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13193 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13194 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13195 s.move_with(|snapshot, sel| {
13196 if sel.is_empty() {
13197 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13198 }
13199 if sel.is_empty() {
13200 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13201 }
13202 });
13203 });
13204 let item = self.cut_common(false, window, cx);
13205 cx.set_global(KillRing(item))
13206 }
13207
13208 pub fn kill_ring_yank(
13209 &mut self,
13210 _: &KillRingYank,
13211 window: &mut Window,
13212 cx: &mut Context<Self>,
13213 ) {
13214 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13215 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13216 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13217 (kill_ring.text().to_string(), kill_ring.metadata_json())
13218 } else {
13219 return;
13220 }
13221 } else {
13222 return;
13223 };
13224 self.do_paste(&text, metadata, false, window, cx);
13225 }
13226
13227 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13228 self.do_copy(true, cx);
13229 }
13230
13231 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13232 self.do_copy(false, cx);
13233 }
13234
13235 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13236 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13237 let buffer = self.buffer.read(cx).read(cx);
13238 let mut text = String::new();
13239
13240 let mut clipboard_selections = Vec::with_capacity(selections.len());
13241 {
13242 let max_point = buffer.max_point();
13243 let mut is_first = true;
13244 let mut prev_selection_was_entire_line = false;
13245 for selection in &selections {
13246 let mut start = selection.start;
13247 let mut end = selection.end;
13248 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13249 let mut add_trailing_newline = false;
13250 if is_entire_line {
13251 start = Point::new(start.row, 0);
13252 let next_line_start = Point::new(end.row + 1, 0);
13253 if next_line_start <= max_point {
13254 end = next_line_start;
13255 } else {
13256 // We're on the last line without a trailing newline.
13257 // Copy to the end of the line and add a newline afterwards.
13258 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13259 add_trailing_newline = true;
13260 }
13261 }
13262
13263 let mut trimmed_selections = Vec::new();
13264 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13265 let row = MultiBufferRow(start.row);
13266 let first_indent = buffer.indent_size_for_line(row);
13267 if first_indent.len == 0 || start.column > first_indent.len {
13268 trimmed_selections.push(start..end);
13269 } else {
13270 trimmed_selections.push(
13271 Point::new(row.0, first_indent.len)
13272 ..Point::new(row.0, buffer.line_len(row)),
13273 );
13274 for row in start.row + 1..=end.row {
13275 let mut line_len = buffer.line_len(MultiBufferRow(row));
13276 if row == end.row {
13277 line_len = end.column;
13278 }
13279 if line_len == 0 {
13280 trimmed_selections
13281 .push(Point::new(row, 0)..Point::new(row, line_len));
13282 continue;
13283 }
13284 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13285 if row_indent_size.len >= first_indent.len {
13286 trimmed_selections.push(
13287 Point::new(row, first_indent.len)..Point::new(row, line_len),
13288 );
13289 } else {
13290 trimmed_selections.clear();
13291 trimmed_selections.push(start..end);
13292 break;
13293 }
13294 }
13295 }
13296 } else {
13297 trimmed_selections.push(start..end);
13298 }
13299
13300 let is_multiline_trim = trimmed_selections.len() > 1;
13301 for trimmed_range in trimmed_selections {
13302 if is_first {
13303 is_first = false;
13304 } else if is_multiline_trim || !prev_selection_was_entire_line {
13305 text += "\n";
13306 }
13307 prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13308 let mut len = 0;
13309 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13310 text.push_str(chunk);
13311 len += chunk.len();
13312 }
13313 if add_trailing_newline {
13314 text.push('\n');
13315 len += 1;
13316 }
13317 clipboard_selections.push(ClipboardSelection::for_buffer(
13318 len,
13319 is_entire_line,
13320 trimmed_range,
13321 &buffer,
13322 self.project.as_ref(),
13323 cx,
13324 ));
13325 }
13326 }
13327 }
13328
13329 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13330 text,
13331 clipboard_selections,
13332 ));
13333 }
13334
13335 pub fn do_paste(
13336 &mut self,
13337 text: &String,
13338 clipboard_selections: Option<Vec<ClipboardSelection>>,
13339 handle_entire_lines: bool,
13340 window: &mut Window,
13341 cx: &mut Context<Self>,
13342 ) {
13343 if self.read_only(cx) {
13344 return;
13345 }
13346
13347 let clipboard_text = Cow::Borrowed(text.as_str());
13348
13349 self.transact(window, cx, |this, window, cx| {
13350 let had_active_edit_prediction = this.has_active_edit_prediction();
13351 let display_map = this.display_snapshot(cx);
13352 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13353 let cursor_offset = this
13354 .selections
13355 .last::<MultiBufferOffset>(&display_map)
13356 .head();
13357
13358 if let Some(mut clipboard_selections) = clipboard_selections {
13359 let all_selections_were_entire_line =
13360 clipboard_selections.iter().all(|s| s.is_entire_line);
13361 let first_selection_indent_column =
13362 clipboard_selections.first().map(|s| s.first_line_indent);
13363 if clipboard_selections.len() != old_selections.len() {
13364 clipboard_selections.drain(..);
13365 }
13366 let mut auto_indent_on_paste = true;
13367
13368 this.buffer.update(cx, |buffer, cx| {
13369 let snapshot = buffer.read(cx);
13370 auto_indent_on_paste = snapshot
13371 .language_settings_at(cursor_offset, cx)
13372 .auto_indent_on_paste;
13373
13374 let mut start_offset = 0;
13375 let mut edits = Vec::new();
13376 let mut original_indent_columns = Vec::new();
13377 for (ix, selection) in old_selections.iter().enumerate() {
13378 let to_insert;
13379 let entire_line;
13380 let original_indent_column;
13381 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13382 let end_offset = start_offset + clipboard_selection.len;
13383 to_insert = &clipboard_text[start_offset..end_offset];
13384 entire_line = clipboard_selection.is_entire_line;
13385 start_offset = if entire_line {
13386 end_offset
13387 } else {
13388 end_offset + 1
13389 };
13390 original_indent_column = Some(clipboard_selection.first_line_indent);
13391 } else {
13392 to_insert = &*clipboard_text;
13393 entire_line = all_selections_were_entire_line;
13394 original_indent_column = first_selection_indent_column
13395 }
13396
13397 let (range, to_insert) =
13398 if selection.is_empty() && handle_entire_lines && entire_line {
13399 // If the corresponding selection was empty when this slice of the
13400 // clipboard text was written, then the entire line containing the
13401 // selection was copied. If this selection is also currently empty,
13402 // then paste the line before the current line of the buffer.
13403 let column = selection.start.to_point(&snapshot).column as usize;
13404 let line_start = selection.start - column;
13405 (line_start..line_start, Cow::Borrowed(to_insert))
13406 } else {
13407 let language = snapshot.language_at(selection.head());
13408 let range = selection.range();
13409 if let Some(language) = language
13410 && language.name() == "Markdown".into()
13411 {
13412 edit_for_markdown_paste(
13413 &snapshot,
13414 range,
13415 to_insert,
13416 url::Url::parse(to_insert).ok(),
13417 )
13418 } else {
13419 (range, Cow::Borrowed(to_insert))
13420 }
13421 };
13422
13423 edits.push((range, to_insert));
13424 original_indent_columns.push(original_indent_column);
13425 }
13426 drop(snapshot);
13427
13428 buffer.edit(
13429 edits,
13430 if auto_indent_on_paste {
13431 Some(AutoindentMode::Block {
13432 original_indent_columns,
13433 })
13434 } else {
13435 None
13436 },
13437 cx,
13438 );
13439 });
13440
13441 let selections = this
13442 .selections
13443 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13444 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13445 } else {
13446 let url = url::Url::parse(&clipboard_text).ok();
13447
13448 let auto_indent_mode = if !clipboard_text.is_empty() {
13449 Some(AutoindentMode::Block {
13450 original_indent_columns: Vec::new(),
13451 })
13452 } else {
13453 None
13454 };
13455
13456 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13457 let snapshot = buffer.snapshot(cx);
13458
13459 let anchors = old_selections
13460 .iter()
13461 .map(|s| {
13462 let anchor = snapshot.anchor_after(s.head());
13463 s.map(|_| anchor)
13464 })
13465 .collect::<Vec<_>>();
13466
13467 let mut edits = Vec::new();
13468
13469 for selection in old_selections.iter() {
13470 let language = snapshot.language_at(selection.head());
13471 let range = selection.range();
13472
13473 let (edit_range, edit_text) = if let Some(language) = language
13474 && language.name() == "Markdown".into()
13475 {
13476 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13477 } else {
13478 (range, clipboard_text.clone())
13479 };
13480
13481 edits.push((edit_range, edit_text));
13482 }
13483
13484 drop(snapshot);
13485 buffer.edit(edits, auto_indent_mode, cx);
13486
13487 anchors
13488 });
13489
13490 this.change_selections(Default::default(), window, cx, |s| {
13491 s.select_anchors(selection_anchors);
13492 });
13493 }
13494
13495 // 🤔 | .. | show_in_menu |
13496 // | .. | true true
13497 // | had_edit_prediction | false true
13498
13499 let trigger_in_words =
13500 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13501
13502 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13503 });
13504 }
13505
13506 pub fn diff_clipboard_with_selection(
13507 &mut self,
13508 _: &DiffClipboardWithSelection,
13509 window: &mut Window,
13510 cx: &mut Context<Self>,
13511 ) {
13512 let selections = self
13513 .selections
13514 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13515
13516 if selections.is_empty() {
13517 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13518 return;
13519 };
13520
13521 let clipboard_text = match cx.read_from_clipboard() {
13522 Some(item) => match item.entries().first() {
13523 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13524 _ => None,
13525 },
13526 None => None,
13527 };
13528
13529 let Some(clipboard_text) = clipboard_text else {
13530 log::warn!("Clipboard doesn't contain text.");
13531 return;
13532 };
13533
13534 window.dispatch_action(
13535 Box::new(DiffClipboardWithSelectionData {
13536 clipboard_text,
13537 editor: cx.entity(),
13538 }),
13539 cx,
13540 );
13541 }
13542
13543 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13545 if let Some(item) = cx.read_from_clipboard() {
13546 let entries = item.entries();
13547
13548 match entries.first() {
13549 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13550 // of all the pasted entries.
13551 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13552 .do_paste(
13553 clipboard_string.text(),
13554 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13555 true,
13556 window,
13557 cx,
13558 ),
13559 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13560 }
13561 }
13562 }
13563
13564 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13565 if self.read_only(cx) {
13566 return;
13567 }
13568
13569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13570
13571 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13572 if let Some((selections, _)) =
13573 self.selection_history.transaction(transaction_id).cloned()
13574 {
13575 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13576 s.select_anchors(selections.to_vec());
13577 });
13578 } else {
13579 log::error!(
13580 "No entry in selection_history found for undo. \
13581 This may correspond to a bug where undo does not update the selection. \
13582 If this is occurring, please add details to \
13583 https://github.com/zed-industries/zed/issues/22692"
13584 );
13585 }
13586 self.request_autoscroll(Autoscroll::fit(), cx);
13587 self.unmark_text(window, cx);
13588 self.refresh_edit_prediction(true, false, window, cx);
13589 cx.emit(EditorEvent::Edited { transaction_id });
13590 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13591 }
13592 }
13593
13594 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13595 if self.read_only(cx) {
13596 return;
13597 }
13598
13599 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13600
13601 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13602 if let Some((_, Some(selections))) =
13603 self.selection_history.transaction(transaction_id).cloned()
13604 {
13605 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13606 s.select_anchors(selections.to_vec());
13607 });
13608 } else {
13609 log::error!(
13610 "No entry in selection_history found for redo. \
13611 This may correspond to a bug where undo does not update the selection. \
13612 If this is occurring, please add details to \
13613 https://github.com/zed-industries/zed/issues/22692"
13614 );
13615 }
13616 self.request_autoscroll(Autoscroll::fit(), cx);
13617 self.unmark_text(window, cx);
13618 self.refresh_edit_prediction(true, false, window, cx);
13619 cx.emit(EditorEvent::Edited { transaction_id });
13620 }
13621 }
13622
13623 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13624 self.buffer
13625 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13626 }
13627
13628 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13629 self.buffer
13630 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13631 }
13632
13633 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13634 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13635 self.change_selections(Default::default(), window, cx, |s| {
13636 s.move_with(|map, selection| {
13637 let cursor = if selection.is_empty() {
13638 movement::left(map, selection.start)
13639 } else {
13640 selection.start
13641 };
13642 selection.collapse_to(cursor, SelectionGoal::None);
13643 });
13644 })
13645 }
13646
13647 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13648 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13649 self.change_selections(Default::default(), window, cx, |s| {
13650 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13651 })
13652 }
13653
13654 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13655 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13656 self.change_selections(Default::default(), window, cx, |s| {
13657 s.move_with(|map, selection| {
13658 let cursor = if selection.is_empty() {
13659 movement::right(map, selection.end)
13660 } else {
13661 selection.end
13662 };
13663 selection.collapse_to(cursor, SelectionGoal::None)
13664 });
13665 })
13666 }
13667
13668 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13669 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13670 self.change_selections(Default::default(), window, cx, |s| {
13671 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13672 });
13673 }
13674
13675 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13676 if self.take_rename(true, window, cx).is_some() {
13677 return;
13678 }
13679
13680 if self.mode.is_single_line() {
13681 cx.propagate();
13682 return;
13683 }
13684
13685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13686
13687 let text_layout_details = &self.text_layout_details(window);
13688 let selection_count = self.selections.count();
13689 let first_selection = self.selections.first_anchor();
13690
13691 self.change_selections(Default::default(), window, cx, |s| {
13692 s.move_with(|map, selection| {
13693 if !selection.is_empty() {
13694 selection.goal = SelectionGoal::None;
13695 }
13696 let (cursor, goal) = movement::up(
13697 map,
13698 selection.start,
13699 selection.goal,
13700 false,
13701 text_layout_details,
13702 );
13703 selection.collapse_to(cursor, goal);
13704 });
13705 });
13706
13707 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13708 {
13709 cx.propagate();
13710 }
13711 }
13712
13713 pub fn move_up_by_lines(
13714 &mut self,
13715 action: &MoveUpByLines,
13716 window: &mut Window,
13717 cx: &mut Context<Self>,
13718 ) {
13719 if self.take_rename(true, window, cx).is_some() {
13720 return;
13721 }
13722
13723 if self.mode.is_single_line() {
13724 cx.propagate();
13725 return;
13726 }
13727
13728 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13729
13730 let text_layout_details = &self.text_layout_details(window);
13731
13732 self.change_selections(Default::default(), window, cx, |s| {
13733 s.move_with(|map, selection| {
13734 if !selection.is_empty() {
13735 selection.goal = SelectionGoal::None;
13736 }
13737 let (cursor, goal) = movement::up_by_rows(
13738 map,
13739 selection.start,
13740 action.lines,
13741 selection.goal,
13742 false,
13743 text_layout_details,
13744 );
13745 selection.collapse_to(cursor, goal);
13746 });
13747 })
13748 }
13749
13750 pub fn move_down_by_lines(
13751 &mut self,
13752 action: &MoveDownByLines,
13753 window: &mut Window,
13754 cx: &mut Context<Self>,
13755 ) {
13756 if self.take_rename(true, window, cx).is_some() {
13757 return;
13758 }
13759
13760 if self.mode.is_single_line() {
13761 cx.propagate();
13762 return;
13763 }
13764
13765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13766
13767 let text_layout_details = &self.text_layout_details(window);
13768
13769 self.change_selections(Default::default(), window, cx, |s| {
13770 s.move_with(|map, selection| {
13771 if !selection.is_empty() {
13772 selection.goal = SelectionGoal::None;
13773 }
13774 let (cursor, goal) = movement::down_by_rows(
13775 map,
13776 selection.start,
13777 action.lines,
13778 selection.goal,
13779 false,
13780 text_layout_details,
13781 );
13782 selection.collapse_to(cursor, goal);
13783 });
13784 })
13785 }
13786
13787 pub fn select_down_by_lines(
13788 &mut self,
13789 action: &SelectDownByLines,
13790 window: &mut Window,
13791 cx: &mut Context<Self>,
13792 ) {
13793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13794 let text_layout_details = &self.text_layout_details(window);
13795 self.change_selections(Default::default(), window, cx, |s| {
13796 s.move_heads_with(|map, head, goal| {
13797 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13798 })
13799 })
13800 }
13801
13802 pub fn select_up_by_lines(
13803 &mut self,
13804 action: &SelectUpByLines,
13805 window: &mut Window,
13806 cx: &mut Context<Self>,
13807 ) {
13808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13809 let text_layout_details = &self.text_layout_details(window);
13810 self.change_selections(Default::default(), window, cx, |s| {
13811 s.move_heads_with(|map, head, goal| {
13812 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13813 })
13814 })
13815 }
13816
13817 pub fn select_page_up(
13818 &mut self,
13819 _: &SelectPageUp,
13820 window: &mut Window,
13821 cx: &mut Context<Self>,
13822 ) {
13823 let Some(row_count) = self.visible_row_count() else {
13824 return;
13825 };
13826
13827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13828
13829 let text_layout_details = &self.text_layout_details(window);
13830
13831 self.change_selections(Default::default(), window, cx, |s| {
13832 s.move_heads_with(|map, head, goal| {
13833 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13834 })
13835 })
13836 }
13837
13838 pub fn move_page_up(
13839 &mut self,
13840 action: &MovePageUp,
13841 window: &mut Window,
13842 cx: &mut Context<Self>,
13843 ) {
13844 if self.take_rename(true, window, cx).is_some() {
13845 return;
13846 }
13847
13848 if self
13849 .context_menu
13850 .borrow_mut()
13851 .as_mut()
13852 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13853 .unwrap_or(false)
13854 {
13855 return;
13856 }
13857
13858 if matches!(self.mode, EditorMode::SingleLine) {
13859 cx.propagate();
13860 return;
13861 }
13862
13863 let Some(row_count) = self.visible_row_count() else {
13864 return;
13865 };
13866
13867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13868
13869 let effects = if action.center_cursor {
13870 SelectionEffects::scroll(Autoscroll::center())
13871 } else {
13872 SelectionEffects::default()
13873 };
13874
13875 let text_layout_details = &self.text_layout_details(window);
13876
13877 self.change_selections(effects, window, cx, |s| {
13878 s.move_with(|map, selection| {
13879 if !selection.is_empty() {
13880 selection.goal = SelectionGoal::None;
13881 }
13882 let (cursor, goal) = movement::up_by_rows(
13883 map,
13884 selection.end,
13885 row_count,
13886 selection.goal,
13887 false,
13888 text_layout_details,
13889 );
13890 selection.collapse_to(cursor, goal);
13891 });
13892 });
13893 }
13894
13895 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13897 let text_layout_details = &self.text_layout_details(window);
13898 self.change_selections(Default::default(), window, cx, |s| {
13899 s.move_heads_with(|map, head, goal| {
13900 movement::up(map, head, goal, false, text_layout_details)
13901 })
13902 })
13903 }
13904
13905 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13906 self.take_rename(true, window, cx);
13907
13908 if self.mode.is_single_line() {
13909 cx.propagate();
13910 return;
13911 }
13912
13913 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13914
13915 let text_layout_details = &self.text_layout_details(window);
13916 let selection_count = self.selections.count();
13917 let first_selection = self.selections.first_anchor();
13918
13919 self.change_selections(Default::default(), window, cx, |s| {
13920 s.move_with(|map, selection| {
13921 if !selection.is_empty() {
13922 selection.goal = SelectionGoal::None;
13923 }
13924 let (cursor, goal) = movement::down(
13925 map,
13926 selection.end,
13927 selection.goal,
13928 false,
13929 text_layout_details,
13930 );
13931 selection.collapse_to(cursor, goal);
13932 });
13933 });
13934
13935 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13936 {
13937 cx.propagate();
13938 }
13939 }
13940
13941 pub fn select_page_down(
13942 &mut self,
13943 _: &SelectPageDown,
13944 window: &mut Window,
13945 cx: &mut Context<Self>,
13946 ) {
13947 let Some(row_count) = self.visible_row_count() else {
13948 return;
13949 };
13950
13951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13952
13953 let text_layout_details = &self.text_layout_details(window);
13954
13955 self.change_selections(Default::default(), window, cx, |s| {
13956 s.move_heads_with(|map, head, goal| {
13957 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13958 })
13959 })
13960 }
13961
13962 pub fn move_page_down(
13963 &mut self,
13964 action: &MovePageDown,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) {
13968 if self.take_rename(true, window, cx).is_some() {
13969 return;
13970 }
13971
13972 if self
13973 .context_menu
13974 .borrow_mut()
13975 .as_mut()
13976 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13977 .unwrap_or(false)
13978 {
13979 return;
13980 }
13981
13982 if matches!(self.mode, EditorMode::SingleLine) {
13983 cx.propagate();
13984 return;
13985 }
13986
13987 let Some(row_count) = self.visible_row_count() else {
13988 return;
13989 };
13990
13991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13992
13993 let effects = if action.center_cursor {
13994 SelectionEffects::scroll(Autoscroll::center())
13995 } else {
13996 SelectionEffects::default()
13997 };
13998
13999 let text_layout_details = &self.text_layout_details(window);
14000 self.change_selections(effects, window, cx, |s| {
14001 s.move_with(|map, selection| {
14002 if !selection.is_empty() {
14003 selection.goal = SelectionGoal::None;
14004 }
14005 let (cursor, goal) = movement::down_by_rows(
14006 map,
14007 selection.end,
14008 row_count,
14009 selection.goal,
14010 false,
14011 text_layout_details,
14012 );
14013 selection.collapse_to(cursor, goal);
14014 });
14015 });
14016 }
14017
14018 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14020 let text_layout_details = &self.text_layout_details(window);
14021 self.change_selections(Default::default(), window, cx, |s| {
14022 s.move_heads_with(|map, head, goal| {
14023 movement::down(map, head, goal, false, text_layout_details)
14024 })
14025 });
14026 }
14027
14028 pub fn context_menu_first(
14029 &mut self,
14030 _: &ContextMenuFirst,
14031 window: &mut Window,
14032 cx: &mut Context<Self>,
14033 ) {
14034 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14035 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14036 }
14037 }
14038
14039 pub fn context_menu_prev(
14040 &mut self,
14041 _: &ContextMenuPrevious,
14042 window: &mut Window,
14043 cx: &mut Context<Self>,
14044 ) {
14045 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14046 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14047 }
14048 }
14049
14050 pub fn context_menu_next(
14051 &mut self,
14052 _: &ContextMenuNext,
14053 window: &mut Window,
14054 cx: &mut Context<Self>,
14055 ) {
14056 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14057 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14058 }
14059 }
14060
14061 pub fn context_menu_last(
14062 &mut self,
14063 _: &ContextMenuLast,
14064 window: &mut Window,
14065 cx: &mut Context<Self>,
14066 ) {
14067 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14068 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14069 }
14070 }
14071
14072 pub fn signature_help_prev(
14073 &mut self,
14074 _: &SignatureHelpPrevious,
14075 _: &mut Window,
14076 cx: &mut Context<Self>,
14077 ) {
14078 if let Some(popover) = self.signature_help_state.popover_mut() {
14079 if popover.current_signature == 0 {
14080 popover.current_signature = popover.signatures.len() - 1;
14081 } else {
14082 popover.current_signature -= 1;
14083 }
14084 cx.notify();
14085 }
14086 }
14087
14088 pub fn signature_help_next(
14089 &mut self,
14090 _: &SignatureHelpNext,
14091 _: &mut Window,
14092 cx: &mut Context<Self>,
14093 ) {
14094 if let Some(popover) = self.signature_help_state.popover_mut() {
14095 if popover.current_signature + 1 == popover.signatures.len() {
14096 popover.current_signature = 0;
14097 } else {
14098 popover.current_signature += 1;
14099 }
14100 cx.notify();
14101 }
14102 }
14103
14104 pub fn move_to_previous_word_start(
14105 &mut self,
14106 _: &MoveToPreviousWordStart,
14107 window: &mut Window,
14108 cx: &mut Context<Self>,
14109 ) {
14110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14111 self.change_selections(Default::default(), window, cx, |s| {
14112 s.move_cursors_with(|map, head, _| {
14113 (
14114 movement::previous_word_start(map, head),
14115 SelectionGoal::None,
14116 )
14117 });
14118 })
14119 }
14120
14121 pub fn move_to_previous_subword_start(
14122 &mut self,
14123 _: &MoveToPreviousSubwordStart,
14124 window: &mut Window,
14125 cx: &mut Context<Self>,
14126 ) {
14127 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14128 self.change_selections(Default::default(), window, cx, |s| {
14129 s.move_cursors_with(|map, head, _| {
14130 (
14131 movement::previous_subword_start(map, head),
14132 SelectionGoal::None,
14133 )
14134 });
14135 })
14136 }
14137
14138 pub fn select_to_previous_word_start(
14139 &mut self,
14140 _: &SelectToPreviousWordStart,
14141 window: &mut Window,
14142 cx: &mut Context<Self>,
14143 ) {
14144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14145 self.change_selections(Default::default(), window, cx, |s| {
14146 s.move_heads_with(|map, head, _| {
14147 (
14148 movement::previous_word_start(map, head),
14149 SelectionGoal::None,
14150 )
14151 });
14152 })
14153 }
14154
14155 pub fn select_to_previous_subword_start(
14156 &mut self,
14157 _: &SelectToPreviousSubwordStart,
14158 window: &mut Window,
14159 cx: &mut Context<Self>,
14160 ) {
14161 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14162 self.change_selections(Default::default(), window, cx, |s| {
14163 s.move_heads_with(|map, head, _| {
14164 (
14165 movement::previous_subword_start(map, head),
14166 SelectionGoal::None,
14167 )
14168 });
14169 })
14170 }
14171
14172 pub fn delete_to_previous_word_start(
14173 &mut self,
14174 action: &DeleteToPreviousWordStart,
14175 window: &mut Window,
14176 cx: &mut Context<Self>,
14177 ) {
14178 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14179 self.transact(window, cx, |this, window, cx| {
14180 this.select_autoclose_pair(window, cx);
14181 this.change_selections(Default::default(), window, cx, |s| {
14182 s.move_with(|map, selection| {
14183 if selection.is_empty() {
14184 let mut cursor = if action.ignore_newlines {
14185 movement::previous_word_start(map, selection.head())
14186 } else {
14187 movement::previous_word_start_or_newline(map, selection.head())
14188 };
14189 cursor = movement::adjust_greedy_deletion(
14190 map,
14191 selection.head(),
14192 cursor,
14193 action.ignore_brackets,
14194 );
14195 selection.set_head(cursor, SelectionGoal::None);
14196 }
14197 });
14198 });
14199 this.insert("", window, cx);
14200 });
14201 }
14202
14203 pub fn delete_to_previous_subword_start(
14204 &mut self,
14205 action: &DeleteToPreviousSubwordStart,
14206 window: &mut Window,
14207 cx: &mut Context<Self>,
14208 ) {
14209 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14210 self.transact(window, cx, |this, window, cx| {
14211 this.select_autoclose_pair(window, cx);
14212 this.change_selections(Default::default(), window, cx, |s| {
14213 s.move_with(|map, selection| {
14214 if selection.is_empty() {
14215 let mut cursor = if action.ignore_newlines {
14216 movement::previous_subword_start(map, selection.head())
14217 } else {
14218 movement::previous_subword_start_or_newline(map, selection.head())
14219 };
14220 cursor = movement::adjust_greedy_deletion(
14221 map,
14222 selection.head(),
14223 cursor,
14224 action.ignore_brackets,
14225 );
14226 selection.set_head(cursor, SelectionGoal::None);
14227 }
14228 });
14229 });
14230 this.insert("", window, cx);
14231 });
14232 }
14233
14234 pub fn move_to_next_word_end(
14235 &mut self,
14236 _: &MoveToNextWordEnd,
14237 window: &mut Window,
14238 cx: &mut Context<Self>,
14239 ) {
14240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14241 self.change_selections(Default::default(), window, cx, |s| {
14242 s.move_cursors_with(|map, head, _| {
14243 (movement::next_word_end(map, head), SelectionGoal::None)
14244 });
14245 })
14246 }
14247
14248 pub fn move_to_next_subword_end(
14249 &mut self,
14250 _: &MoveToNextSubwordEnd,
14251 window: &mut Window,
14252 cx: &mut Context<Self>,
14253 ) {
14254 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14255 self.change_selections(Default::default(), window, cx, |s| {
14256 s.move_cursors_with(|map, head, _| {
14257 (movement::next_subword_end(map, head), SelectionGoal::None)
14258 });
14259 })
14260 }
14261
14262 pub fn select_to_next_word_end(
14263 &mut self,
14264 _: &SelectToNextWordEnd,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) {
14268 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14269 self.change_selections(Default::default(), window, cx, |s| {
14270 s.move_heads_with(|map, head, _| {
14271 (movement::next_word_end(map, head), SelectionGoal::None)
14272 });
14273 })
14274 }
14275
14276 pub fn select_to_next_subword_end(
14277 &mut self,
14278 _: &SelectToNextSubwordEnd,
14279 window: &mut Window,
14280 cx: &mut Context<Self>,
14281 ) {
14282 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14283 self.change_selections(Default::default(), window, cx, |s| {
14284 s.move_heads_with(|map, head, _| {
14285 (movement::next_subword_end(map, head), SelectionGoal::None)
14286 });
14287 })
14288 }
14289
14290 pub fn delete_to_next_word_end(
14291 &mut self,
14292 action: &DeleteToNextWordEnd,
14293 window: &mut Window,
14294 cx: &mut Context<Self>,
14295 ) {
14296 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14297 self.transact(window, cx, |this, window, cx| {
14298 this.change_selections(Default::default(), window, cx, |s| {
14299 s.move_with(|map, selection| {
14300 if selection.is_empty() {
14301 let mut cursor = if action.ignore_newlines {
14302 movement::next_word_end(map, selection.head())
14303 } else {
14304 movement::next_word_end_or_newline(map, selection.head())
14305 };
14306 cursor = movement::adjust_greedy_deletion(
14307 map,
14308 selection.head(),
14309 cursor,
14310 action.ignore_brackets,
14311 );
14312 selection.set_head(cursor, SelectionGoal::None);
14313 }
14314 });
14315 });
14316 this.insert("", window, cx);
14317 });
14318 }
14319
14320 pub fn delete_to_next_subword_end(
14321 &mut self,
14322 action: &DeleteToNextSubwordEnd,
14323 window: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) {
14326 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14327 self.transact(window, cx, |this, window, cx| {
14328 this.change_selections(Default::default(), window, cx, |s| {
14329 s.move_with(|map, selection| {
14330 if selection.is_empty() {
14331 let mut cursor = if action.ignore_newlines {
14332 movement::next_subword_end(map, selection.head())
14333 } else {
14334 movement::next_subword_end_or_newline(map, selection.head())
14335 };
14336 cursor = movement::adjust_greedy_deletion(
14337 map,
14338 selection.head(),
14339 cursor,
14340 action.ignore_brackets,
14341 );
14342 selection.set_head(cursor, SelectionGoal::None);
14343 }
14344 });
14345 });
14346 this.insert("", window, cx);
14347 });
14348 }
14349
14350 pub fn move_to_beginning_of_line(
14351 &mut self,
14352 action: &MoveToBeginningOfLine,
14353 window: &mut Window,
14354 cx: &mut Context<Self>,
14355 ) {
14356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14357 self.change_selections(Default::default(), window, cx, |s| {
14358 s.move_cursors_with(|map, head, _| {
14359 (
14360 movement::indented_line_beginning(
14361 map,
14362 head,
14363 action.stop_at_soft_wraps,
14364 action.stop_at_indent,
14365 ),
14366 SelectionGoal::None,
14367 )
14368 });
14369 })
14370 }
14371
14372 pub fn select_to_beginning_of_line(
14373 &mut self,
14374 action: &SelectToBeginningOfLine,
14375 window: &mut Window,
14376 cx: &mut Context<Self>,
14377 ) {
14378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14379 self.change_selections(Default::default(), window, cx, |s| {
14380 s.move_heads_with(|map, head, _| {
14381 (
14382 movement::indented_line_beginning(
14383 map,
14384 head,
14385 action.stop_at_soft_wraps,
14386 action.stop_at_indent,
14387 ),
14388 SelectionGoal::None,
14389 )
14390 });
14391 });
14392 }
14393
14394 pub fn delete_to_beginning_of_line(
14395 &mut self,
14396 action: &DeleteToBeginningOfLine,
14397 window: &mut Window,
14398 cx: &mut Context<Self>,
14399 ) {
14400 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14401 self.transact(window, cx, |this, window, cx| {
14402 this.change_selections(Default::default(), window, cx, |s| {
14403 s.move_with(|_, selection| {
14404 selection.reversed = true;
14405 });
14406 });
14407
14408 this.select_to_beginning_of_line(
14409 &SelectToBeginningOfLine {
14410 stop_at_soft_wraps: false,
14411 stop_at_indent: action.stop_at_indent,
14412 },
14413 window,
14414 cx,
14415 );
14416 this.backspace(&Backspace, window, cx);
14417 });
14418 }
14419
14420 pub fn move_to_end_of_line(
14421 &mut self,
14422 action: &MoveToEndOfLine,
14423 window: &mut Window,
14424 cx: &mut Context<Self>,
14425 ) {
14426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14427 self.change_selections(Default::default(), window, cx, |s| {
14428 s.move_cursors_with(|map, head, _| {
14429 (
14430 movement::line_end(map, head, action.stop_at_soft_wraps),
14431 SelectionGoal::None,
14432 )
14433 });
14434 })
14435 }
14436
14437 pub fn select_to_end_of_line(
14438 &mut self,
14439 action: &SelectToEndOfLine,
14440 window: &mut Window,
14441 cx: &mut Context<Self>,
14442 ) {
14443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14444 self.change_selections(Default::default(), window, cx, |s| {
14445 s.move_heads_with(|map, head, _| {
14446 (
14447 movement::line_end(map, head, action.stop_at_soft_wraps),
14448 SelectionGoal::None,
14449 )
14450 });
14451 })
14452 }
14453
14454 pub fn delete_to_end_of_line(
14455 &mut self,
14456 _: &DeleteToEndOfLine,
14457 window: &mut Window,
14458 cx: &mut Context<Self>,
14459 ) {
14460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14461 self.transact(window, cx, |this, window, cx| {
14462 this.select_to_end_of_line(
14463 &SelectToEndOfLine {
14464 stop_at_soft_wraps: false,
14465 },
14466 window,
14467 cx,
14468 );
14469 this.delete(&Delete, window, cx);
14470 });
14471 }
14472
14473 pub fn cut_to_end_of_line(
14474 &mut self,
14475 action: &CutToEndOfLine,
14476 window: &mut Window,
14477 cx: &mut Context<Self>,
14478 ) {
14479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14480 self.transact(window, cx, |this, window, cx| {
14481 this.select_to_end_of_line(
14482 &SelectToEndOfLine {
14483 stop_at_soft_wraps: false,
14484 },
14485 window,
14486 cx,
14487 );
14488 if !action.stop_at_newlines {
14489 this.change_selections(Default::default(), window, cx, |s| {
14490 s.move_with(|_, sel| {
14491 if sel.is_empty() {
14492 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14493 }
14494 });
14495 });
14496 }
14497 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14498 let item = this.cut_common(false, window, cx);
14499 cx.write_to_clipboard(item);
14500 });
14501 }
14502
14503 pub fn move_to_start_of_paragraph(
14504 &mut self,
14505 _: &MoveToStartOfParagraph,
14506 window: &mut Window,
14507 cx: &mut Context<Self>,
14508 ) {
14509 if matches!(self.mode, EditorMode::SingleLine) {
14510 cx.propagate();
14511 return;
14512 }
14513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14514 self.change_selections(Default::default(), window, cx, |s| {
14515 s.move_with(|map, selection| {
14516 selection.collapse_to(
14517 movement::start_of_paragraph(map, selection.head(), 1),
14518 SelectionGoal::None,
14519 )
14520 });
14521 })
14522 }
14523
14524 pub fn move_to_end_of_paragraph(
14525 &mut self,
14526 _: &MoveToEndOfParagraph,
14527 window: &mut Window,
14528 cx: &mut Context<Self>,
14529 ) {
14530 if matches!(self.mode, EditorMode::SingleLine) {
14531 cx.propagate();
14532 return;
14533 }
14534 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14535 self.change_selections(Default::default(), window, cx, |s| {
14536 s.move_with(|map, selection| {
14537 selection.collapse_to(
14538 movement::end_of_paragraph(map, selection.head(), 1),
14539 SelectionGoal::None,
14540 )
14541 });
14542 })
14543 }
14544
14545 pub fn select_to_start_of_paragraph(
14546 &mut self,
14547 _: &SelectToStartOfParagraph,
14548 window: &mut Window,
14549 cx: &mut Context<Self>,
14550 ) {
14551 if matches!(self.mode, EditorMode::SingleLine) {
14552 cx.propagate();
14553 return;
14554 }
14555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14556 self.change_selections(Default::default(), window, cx, |s| {
14557 s.move_heads_with(|map, head, _| {
14558 (
14559 movement::start_of_paragraph(map, head, 1),
14560 SelectionGoal::None,
14561 )
14562 });
14563 })
14564 }
14565
14566 pub fn select_to_end_of_paragraph(
14567 &mut self,
14568 _: &SelectToEndOfParagraph,
14569 window: &mut Window,
14570 cx: &mut Context<Self>,
14571 ) {
14572 if matches!(self.mode, EditorMode::SingleLine) {
14573 cx.propagate();
14574 return;
14575 }
14576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14577 self.change_selections(Default::default(), window, cx, |s| {
14578 s.move_heads_with(|map, head, _| {
14579 (
14580 movement::end_of_paragraph(map, head, 1),
14581 SelectionGoal::None,
14582 )
14583 });
14584 })
14585 }
14586
14587 pub fn move_to_start_of_excerpt(
14588 &mut self,
14589 _: &MoveToStartOfExcerpt,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) {
14593 if matches!(self.mode, EditorMode::SingleLine) {
14594 cx.propagate();
14595 return;
14596 }
14597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14598 self.change_selections(Default::default(), window, cx, |s| {
14599 s.move_with(|map, selection| {
14600 selection.collapse_to(
14601 movement::start_of_excerpt(
14602 map,
14603 selection.head(),
14604 workspace::searchable::Direction::Prev,
14605 ),
14606 SelectionGoal::None,
14607 )
14608 });
14609 })
14610 }
14611
14612 pub fn move_to_start_of_next_excerpt(
14613 &mut self,
14614 _: &MoveToStartOfNextExcerpt,
14615 window: &mut Window,
14616 cx: &mut Context<Self>,
14617 ) {
14618 if matches!(self.mode, EditorMode::SingleLine) {
14619 cx.propagate();
14620 return;
14621 }
14622
14623 self.change_selections(Default::default(), window, cx, |s| {
14624 s.move_with(|map, selection| {
14625 selection.collapse_to(
14626 movement::start_of_excerpt(
14627 map,
14628 selection.head(),
14629 workspace::searchable::Direction::Next,
14630 ),
14631 SelectionGoal::None,
14632 )
14633 });
14634 })
14635 }
14636
14637 pub fn move_to_end_of_excerpt(
14638 &mut self,
14639 _: &MoveToEndOfExcerpt,
14640 window: &mut Window,
14641 cx: &mut Context<Self>,
14642 ) {
14643 if matches!(self.mode, EditorMode::SingleLine) {
14644 cx.propagate();
14645 return;
14646 }
14647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14648 self.change_selections(Default::default(), window, cx, |s| {
14649 s.move_with(|map, selection| {
14650 selection.collapse_to(
14651 movement::end_of_excerpt(
14652 map,
14653 selection.head(),
14654 workspace::searchable::Direction::Next,
14655 ),
14656 SelectionGoal::None,
14657 )
14658 });
14659 })
14660 }
14661
14662 pub fn move_to_end_of_previous_excerpt(
14663 &mut self,
14664 _: &MoveToEndOfPreviousExcerpt,
14665 window: &mut Window,
14666 cx: &mut Context<Self>,
14667 ) {
14668 if matches!(self.mode, EditorMode::SingleLine) {
14669 cx.propagate();
14670 return;
14671 }
14672 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14673 self.change_selections(Default::default(), window, cx, |s| {
14674 s.move_with(|map, selection| {
14675 selection.collapse_to(
14676 movement::end_of_excerpt(
14677 map,
14678 selection.head(),
14679 workspace::searchable::Direction::Prev,
14680 ),
14681 SelectionGoal::None,
14682 )
14683 });
14684 })
14685 }
14686
14687 pub fn select_to_start_of_excerpt(
14688 &mut self,
14689 _: &SelectToStartOfExcerpt,
14690 window: &mut Window,
14691 cx: &mut Context<Self>,
14692 ) {
14693 if matches!(self.mode, EditorMode::SingleLine) {
14694 cx.propagate();
14695 return;
14696 }
14697 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14698 self.change_selections(Default::default(), window, cx, |s| {
14699 s.move_heads_with(|map, head, _| {
14700 (
14701 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14702 SelectionGoal::None,
14703 )
14704 });
14705 })
14706 }
14707
14708 pub fn select_to_start_of_next_excerpt(
14709 &mut self,
14710 _: &SelectToStartOfNextExcerpt,
14711 window: &mut Window,
14712 cx: &mut Context<Self>,
14713 ) {
14714 if matches!(self.mode, EditorMode::SingleLine) {
14715 cx.propagate();
14716 return;
14717 }
14718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14719 self.change_selections(Default::default(), window, cx, |s| {
14720 s.move_heads_with(|map, head, _| {
14721 (
14722 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14723 SelectionGoal::None,
14724 )
14725 });
14726 })
14727 }
14728
14729 pub fn select_to_end_of_excerpt(
14730 &mut self,
14731 _: &SelectToEndOfExcerpt,
14732 window: &mut Window,
14733 cx: &mut Context<Self>,
14734 ) {
14735 if matches!(self.mode, EditorMode::SingleLine) {
14736 cx.propagate();
14737 return;
14738 }
14739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14740 self.change_selections(Default::default(), window, cx, |s| {
14741 s.move_heads_with(|map, head, _| {
14742 (
14743 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14744 SelectionGoal::None,
14745 )
14746 });
14747 })
14748 }
14749
14750 pub fn select_to_end_of_previous_excerpt(
14751 &mut self,
14752 _: &SelectToEndOfPreviousExcerpt,
14753 window: &mut Window,
14754 cx: &mut Context<Self>,
14755 ) {
14756 if matches!(self.mode, EditorMode::SingleLine) {
14757 cx.propagate();
14758 return;
14759 }
14760 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14761 self.change_selections(Default::default(), window, cx, |s| {
14762 s.move_heads_with(|map, head, _| {
14763 (
14764 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14765 SelectionGoal::None,
14766 )
14767 });
14768 })
14769 }
14770
14771 pub fn move_to_beginning(
14772 &mut self,
14773 _: &MoveToBeginning,
14774 window: &mut Window,
14775 cx: &mut Context<Self>,
14776 ) {
14777 if matches!(self.mode, EditorMode::SingleLine) {
14778 cx.propagate();
14779 return;
14780 }
14781 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14782 self.change_selections(Default::default(), window, cx, |s| {
14783 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14784 });
14785 }
14786
14787 pub fn select_to_beginning(
14788 &mut self,
14789 _: &SelectToBeginning,
14790 window: &mut Window,
14791 cx: &mut Context<Self>,
14792 ) {
14793 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14794 selection.set_head(Point::zero(), SelectionGoal::None);
14795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14796 self.change_selections(Default::default(), window, cx, |s| {
14797 s.select(vec![selection]);
14798 });
14799 }
14800
14801 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14802 if matches!(self.mode, EditorMode::SingleLine) {
14803 cx.propagate();
14804 return;
14805 }
14806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14807 let cursor = self.buffer.read(cx).read(cx).len();
14808 self.change_selections(Default::default(), window, cx, |s| {
14809 s.select_ranges(vec![cursor..cursor])
14810 });
14811 }
14812
14813 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14814 self.nav_history = nav_history;
14815 }
14816
14817 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14818 self.nav_history.as_ref()
14819 }
14820
14821 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14822 self.push_to_nav_history(
14823 self.selections.newest_anchor().head(),
14824 None,
14825 false,
14826 true,
14827 cx,
14828 );
14829 }
14830
14831 fn push_to_nav_history(
14832 &mut self,
14833 cursor_anchor: Anchor,
14834 new_position: Option<Point>,
14835 is_deactivate: bool,
14836 always: bool,
14837 cx: &mut Context<Self>,
14838 ) {
14839 if let Some(nav_history) = self.nav_history.as_mut() {
14840 let buffer = self.buffer.read(cx).read(cx);
14841 let cursor_position = cursor_anchor.to_point(&buffer);
14842 let scroll_state = self.scroll_manager.anchor();
14843 let scroll_top_row = scroll_state.top_row(&buffer);
14844 drop(buffer);
14845
14846 if let Some(new_position) = new_position {
14847 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14848 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14849 return;
14850 }
14851 }
14852
14853 nav_history.push(
14854 Some(NavigationData {
14855 cursor_anchor,
14856 cursor_position,
14857 scroll_anchor: scroll_state,
14858 scroll_top_row,
14859 }),
14860 cx,
14861 );
14862 cx.emit(EditorEvent::PushedToNavHistory {
14863 anchor: cursor_anchor,
14864 is_deactivate,
14865 })
14866 }
14867 }
14868
14869 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14870 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14871 let buffer = self.buffer.read(cx).snapshot(cx);
14872 let mut selection = self
14873 .selections
14874 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14875 selection.set_head(buffer.len(), SelectionGoal::None);
14876 self.change_selections(Default::default(), window, cx, |s| {
14877 s.select(vec![selection]);
14878 });
14879 }
14880
14881 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14883 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14884 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14885 });
14886 }
14887
14888 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14890 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14891 let mut selections = self.selections.all::<Point>(&display_map);
14892 let max_point = display_map.buffer_snapshot().max_point();
14893 for selection in &mut selections {
14894 let rows = selection.spanned_rows(true, &display_map);
14895 selection.start = Point::new(rows.start.0, 0);
14896 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14897 selection.reversed = false;
14898 }
14899 self.change_selections(Default::default(), window, cx, |s| {
14900 s.select(selections);
14901 });
14902 }
14903
14904 pub fn split_selection_into_lines(
14905 &mut self,
14906 action: &SplitSelectionIntoLines,
14907 window: &mut Window,
14908 cx: &mut Context<Self>,
14909 ) {
14910 let selections = self
14911 .selections
14912 .all::<Point>(&self.display_snapshot(cx))
14913 .into_iter()
14914 .map(|selection| selection.start..selection.end)
14915 .collect::<Vec<_>>();
14916 self.unfold_ranges(&selections, true, true, cx);
14917
14918 let mut new_selection_ranges = Vec::new();
14919 {
14920 let buffer = self.buffer.read(cx).read(cx);
14921 for selection in selections {
14922 for row in selection.start.row..selection.end.row {
14923 let line_start = Point::new(row, 0);
14924 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14925
14926 if action.keep_selections {
14927 // Keep the selection range for each line
14928 let selection_start = if row == selection.start.row {
14929 selection.start
14930 } else {
14931 line_start
14932 };
14933 new_selection_ranges.push(selection_start..line_end);
14934 } else {
14935 // Collapse to cursor at end of line
14936 new_selection_ranges.push(line_end..line_end);
14937 }
14938 }
14939
14940 let is_multiline_selection = selection.start.row != selection.end.row;
14941 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14942 // so this action feels more ergonomic when paired with other selection operations
14943 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14944 if !should_skip_last {
14945 if action.keep_selections {
14946 if is_multiline_selection {
14947 let line_start = Point::new(selection.end.row, 0);
14948 new_selection_ranges.push(line_start..selection.end);
14949 } else {
14950 new_selection_ranges.push(selection.start..selection.end);
14951 }
14952 } else {
14953 new_selection_ranges.push(selection.end..selection.end);
14954 }
14955 }
14956 }
14957 }
14958 self.change_selections(Default::default(), window, cx, |s| {
14959 s.select_ranges(new_selection_ranges);
14960 });
14961 }
14962
14963 pub fn add_selection_above(
14964 &mut self,
14965 action: &AddSelectionAbove,
14966 window: &mut Window,
14967 cx: &mut Context<Self>,
14968 ) {
14969 self.add_selection(true, action.skip_soft_wrap, window, cx);
14970 }
14971
14972 pub fn add_selection_below(
14973 &mut self,
14974 action: &AddSelectionBelow,
14975 window: &mut Window,
14976 cx: &mut Context<Self>,
14977 ) {
14978 self.add_selection(false, action.skip_soft_wrap, window, cx);
14979 }
14980
14981 fn add_selection(
14982 &mut self,
14983 above: bool,
14984 skip_soft_wrap: bool,
14985 window: &mut Window,
14986 cx: &mut Context<Self>,
14987 ) {
14988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14989
14990 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14991 let all_selections = self.selections.all::<Point>(&display_map);
14992 let text_layout_details = self.text_layout_details(window);
14993
14994 let (mut columnar_selections, new_selections_to_columnarize) = {
14995 if let Some(state) = self.add_selections_state.as_ref() {
14996 let columnar_selection_ids: HashSet<_> = state
14997 .groups
14998 .iter()
14999 .flat_map(|group| group.stack.iter())
15000 .copied()
15001 .collect();
15002
15003 all_selections
15004 .into_iter()
15005 .partition(|s| columnar_selection_ids.contains(&s.id))
15006 } else {
15007 (Vec::new(), all_selections)
15008 }
15009 };
15010
15011 let mut state = self
15012 .add_selections_state
15013 .take()
15014 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15015
15016 for selection in new_selections_to_columnarize {
15017 let range = selection.display_range(&display_map).sorted();
15018 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15019 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15020 let positions = start_x.min(end_x)..start_x.max(end_x);
15021 let mut stack = Vec::new();
15022 for row in range.start.row().0..=range.end.row().0 {
15023 if let Some(selection) = self.selections.build_columnar_selection(
15024 &display_map,
15025 DisplayRow(row),
15026 &positions,
15027 selection.reversed,
15028 &text_layout_details,
15029 ) {
15030 stack.push(selection.id);
15031 columnar_selections.push(selection);
15032 }
15033 }
15034 if !stack.is_empty() {
15035 if above {
15036 stack.reverse();
15037 }
15038 state.groups.push(AddSelectionsGroup { above, stack });
15039 }
15040 }
15041
15042 let mut final_selections = Vec::new();
15043 let end_row = if above {
15044 DisplayRow(0)
15045 } else {
15046 display_map.max_point().row()
15047 };
15048
15049 let mut last_added_item_per_group = HashMap::default();
15050 for group in state.groups.iter_mut() {
15051 if let Some(last_id) = group.stack.last() {
15052 last_added_item_per_group.insert(*last_id, group);
15053 }
15054 }
15055
15056 for selection in columnar_selections {
15057 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15058 if above == group.above {
15059 let range = selection.display_range(&display_map).sorted();
15060 debug_assert_eq!(range.start.row(), range.end.row());
15061 let mut row = range.start.row();
15062 let positions =
15063 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15064 Pixels::from(start)..Pixels::from(end)
15065 } else {
15066 let start_x =
15067 display_map.x_for_display_point(range.start, &text_layout_details);
15068 let end_x =
15069 display_map.x_for_display_point(range.end, &text_layout_details);
15070 start_x.min(end_x)..start_x.max(end_x)
15071 };
15072
15073 let mut maybe_new_selection = None;
15074 let direction = if above { -1 } else { 1 };
15075
15076 while row != end_row {
15077 let new_buffer_row = if skip_soft_wrap {
15078 let new_row = display_map
15079 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
15080 row = new_row.row();
15081 Some(new_row.to_point(&display_map).row)
15082 } else {
15083 if above {
15084 row.0 -= 1;
15085 } else {
15086 row.0 += 1;
15087 }
15088 None
15089 };
15090
15091 let new_selection = if let Some(buffer_row) = new_buffer_row {
15092 let start_col = selection.start.column;
15093 let end_col = selection.end.column;
15094 let buffer_columns = start_col.min(end_col)..start_col.max(end_col);
15095
15096 self.selections
15097 .build_columnar_selection_from_buffer_columns(
15098 &display_map,
15099 buffer_row,
15100 &buffer_columns,
15101 selection.reversed,
15102 &text_layout_details,
15103 )
15104 } else {
15105 self.selections.build_columnar_selection(
15106 &display_map,
15107 row,
15108 &positions,
15109 selection.reversed,
15110 &text_layout_details,
15111 )
15112 };
15113
15114 if let Some(new_selection) = new_selection {
15115 maybe_new_selection = Some(new_selection);
15116 break;
15117 }
15118 }
15119
15120 if let Some(new_selection) = maybe_new_selection {
15121 group.stack.push(new_selection.id);
15122 if above {
15123 final_selections.push(new_selection);
15124 final_selections.push(selection);
15125 } else {
15126 final_selections.push(selection);
15127 final_selections.push(new_selection);
15128 }
15129 } else {
15130 final_selections.push(selection);
15131 }
15132 } else {
15133 group.stack.pop();
15134 }
15135 } else {
15136 final_selections.push(selection);
15137 }
15138 }
15139
15140 self.change_selections(Default::default(), window, cx, |s| {
15141 s.select(final_selections);
15142 });
15143
15144 let final_selection_ids: HashSet<_> = self
15145 .selections
15146 .all::<Point>(&display_map)
15147 .iter()
15148 .map(|s| s.id)
15149 .collect();
15150 state.groups.retain_mut(|group| {
15151 // selections might get merged above so we remove invalid items from stacks
15152 group.stack.retain(|id| final_selection_ids.contains(id));
15153
15154 // single selection in stack can be treated as initial state
15155 group.stack.len() > 1
15156 });
15157
15158 if !state.groups.is_empty() {
15159 self.add_selections_state = Some(state);
15160 }
15161 }
15162
15163 pub fn insert_snippet_at_selections(
15164 &mut self,
15165 action: &InsertSnippet,
15166 window: &mut Window,
15167 cx: &mut Context<Self>,
15168 ) {
15169 self.try_insert_snippet_at_selections(action, window, cx)
15170 .log_err();
15171 }
15172
15173 fn try_insert_snippet_at_selections(
15174 &mut self,
15175 action: &InsertSnippet,
15176 window: &mut Window,
15177 cx: &mut Context<Self>,
15178 ) -> Result<()> {
15179 let insertion_ranges = self
15180 .selections
15181 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15182 .into_iter()
15183 .map(|selection| selection.range())
15184 .collect_vec();
15185
15186 let snippet = if let Some(snippet_body) = &action.snippet {
15187 if action.language.is_none() && action.name.is_none() {
15188 Snippet::parse(snippet_body)?
15189 } else {
15190 bail!("`snippet` is mutually exclusive with `language` and `name`")
15191 }
15192 } else if let Some(name) = &action.name {
15193 let project = self.project().context("no project")?;
15194 let snippet_store = project.read(cx).snippets().read(cx);
15195 let snippet = snippet_store
15196 .snippets_for(action.language.clone(), cx)
15197 .into_iter()
15198 .find(|snippet| snippet.name == *name)
15199 .context("snippet not found")?;
15200 Snippet::parse(&snippet.body)?
15201 } else {
15202 // todo(andrew): open modal to select snippet
15203 bail!("`name` or `snippet` is required")
15204 };
15205
15206 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15207 }
15208
15209 fn select_match_ranges(
15210 &mut self,
15211 range: Range<MultiBufferOffset>,
15212 reversed: bool,
15213 replace_newest: bool,
15214 auto_scroll: Option<Autoscroll>,
15215 window: &mut Window,
15216 cx: &mut Context<Editor>,
15217 ) {
15218 self.unfold_ranges(
15219 std::slice::from_ref(&range),
15220 false,
15221 auto_scroll.is_some(),
15222 cx,
15223 );
15224 let effects = if let Some(scroll) = auto_scroll {
15225 SelectionEffects::scroll(scroll)
15226 } else {
15227 SelectionEffects::no_scroll()
15228 };
15229 self.change_selections(effects, window, cx, |s| {
15230 if replace_newest {
15231 s.delete(s.newest_anchor().id);
15232 }
15233 if reversed {
15234 s.insert_range(range.end..range.start);
15235 } else {
15236 s.insert_range(range);
15237 }
15238 });
15239 }
15240
15241 pub fn select_next_match_internal(
15242 &mut self,
15243 display_map: &DisplaySnapshot,
15244 replace_newest: bool,
15245 autoscroll: Option<Autoscroll>,
15246 window: &mut Window,
15247 cx: &mut Context<Self>,
15248 ) -> Result<()> {
15249 let buffer = display_map.buffer_snapshot();
15250 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15251 if let Some(mut select_next_state) = self.select_next_state.take() {
15252 let query = &select_next_state.query;
15253 if !select_next_state.done {
15254 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15255 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15256 let mut next_selected_range = None;
15257
15258 let bytes_after_last_selection =
15259 buffer.bytes_in_range(last_selection.end..buffer.len());
15260 let bytes_before_first_selection =
15261 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15262 let query_matches = query
15263 .stream_find_iter(bytes_after_last_selection)
15264 .map(|result| (last_selection.end, result))
15265 .chain(
15266 query
15267 .stream_find_iter(bytes_before_first_selection)
15268 .map(|result| (MultiBufferOffset(0), result)),
15269 );
15270
15271 for (start_offset, query_match) in query_matches {
15272 let query_match = query_match.unwrap(); // can only fail due to I/O
15273 let offset_range =
15274 start_offset + query_match.start()..start_offset + query_match.end();
15275
15276 if !select_next_state.wordwise
15277 || (!buffer.is_inside_word(offset_range.start, None)
15278 && !buffer.is_inside_word(offset_range.end, None))
15279 {
15280 let idx = selections
15281 .partition_point(|selection| selection.end <= offset_range.start);
15282 let overlaps = selections
15283 .get(idx)
15284 .map_or(false, |selection| selection.start < offset_range.end);
15285
15286 if !overlaps {
15287 next_selected_range = Some(offset_range);
15288 break;
15289 }
15290 }
15291 }
15292
15293 if let Some(next_selected_range) = next_selected_range {
15294 self.select_match_ranges(
15295 next_selected_range,
15296 last_selection.reversed,
15297 replace_newest,
15298 autoscroll,
15299 window,
15300 cx,
15301 );
15302 } else {
15303 select_next_state.done = true;
15304 }
15305 }
15306
15307 self.select_next_state = Some(select_next_state);
15308 } else {
15309 let mut only_carets = true;
15310 let mut same_text_selected = true;
15311 let mut selected_text = None;
15312
15313 let mut selections_iter = selections.iter().peekable();
15314 while let Some(selection) = selections_iter.next() {
15315 if selection.start != selection.end {
15316 only_carets = false;
15317 }
15318
15319 if same_text_selected {
15320 if selected_text.is_none() {
15321 selected_text =
15322 Some(buffer.text_for_range(selection.range()).collect::<String>());
15323 }
15324
15325 if let Some(next_selection) = selections_iter.peek() {
15326 if next_selection.len() == selection.len() {
15327 let next_selected_text = buffer
15328 .text_for_range(next_selection.range())
15329 .collect::<String>();
15330 if Some(next_selected_text) != selected_text {
15331 same_text_selected = false;
15332 selected_text = None;
15333 }
15334 } else {
15335 same_text_selected = false;
15336 selected_text = None;
15337 }
15338 }
15339 }
15340 }
15341
15342 if only_carets {
15343 for selection in &mut selections {
15344 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15345 selection.start = word_range.start;
15346 selection.end = word_range.end;
15347 selection.goal = SelectionGoal::None;
15348 selection.reversed = false;
15349 self.select_match_ranges(
15350 selection.start..selection.end,
15351 selection.reversed,
15352 replace_newest,
15353 autoscroll,
15354 window,
15355 cx,
15356 );
15357 }
15358
15359 if selections.len() == 1 {
15360 let selection = selections
15361 .last()
15362 .expect("ensured that there's only one selection");
15363 let query = buffer
15364 .text_for_range(selection.start..selection.end)
15365 .collect::<String>();
15366 let is_empty = query.is_empty();
15367 let select_state = SelectNextState {
15368 query: self.build_query(&[query], cx)?,
15369 wordwise: true,
15370 done: is_empty,
15371 };
15372 self.select_next_state = Some(select_state);
15373 } else {
15374 self.select_next_state = None;
15375 }
15376 } else if let Some(selected_text) = selected_text {
15377 self.select_next_state = Some(SelectNextState {
15378 query: self.build_query(&[selected_text], cx)?,
15379 wordwise: false,
15380 done: false,
15381 });
15382 self.select_next_match_internal(
15383 display_map,
15384 replace_newest,
15385 autoscroll,
15386 window,
15387 cx,
15388 )?;
15389 }
15390 }
15391 Ok(())
15392 }
15393
15394 pub fn select_all_matches(
15395 &mut self,
15396 _action: &SelectAllMatches,
15397 window: &mut Window,
15398 cx: &mut Context<Self>,
15399 ) -> Result<()> {
15400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15401
15402 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15403
15404 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15405 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15406 else {
15407 return Ok(());
15408 };
15409
15410 let mut new_selections = Vec::new();
15411
15412 let reversed = self
15413 .selections
15414 .oldest::<MultiBufferOffset>(&display_map)
15415 .reversed;
15416 let buffer = display_map.buffer_snapshot();
15417 let query_matches = select_next_state
15418 .query
15419 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15420
15421 for query_match in query_matches.into_iter() {
15422 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15423 let offset_range = if reversed {
15424 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15425 } else {
15426 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15427 };
15428
15429 if !select_next_state.wordwise
15430 || (!buffer.is_inside_word(offset_range.start, None)
15431 && !buffer.is_inside_word(offset_range.end, None))
15432 {
15433 new_selections.push(offset_range.start..offset_range.end);
15434 }
15435 }
15436
15437 select_next_state.done = true;
15438
15439 if new_selections.is_empty() {
15440 log::error!("bug: new_selections is empty in select_all_matches");
15441 return Ok(());
15442 }
15443
15444 self.unfold_ranges(&new_selections, false, false, cx);
15445 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15446 selections.select_ranges(new_selections)
15447 });
15448
15449 Ok(())
15450 }
15451
15452 pub fn select_next(
15453 &mut self,
15454 action: &SelectNext,
15455 window: &mut Window,
15456 cx: &mut Context<Self>,
15457 ) -> Result<()> {
15458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15459 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15460 self.select_next_match_internal(
15461 &display_map,
15462 action.replace_newest,
15463 Some(Autoscroll::newest()),
15464 window,
15465 cx,
15466 )
15467 }
15468
15469 pub fn select_previous(
15470 &mut self,
15471 action: &SelectPrevious,
15472 window: &mut Window,
15473 cx: &mut Context<Self>,
15474 ) -> Result<()> {
15475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15477 let buffer = display_map.buffer_snapshot();
15478 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15479 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15480 let query = &select_prev_state.query;
15481 if !select_prev_state.done {
15482 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15483 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15484 let mut next_selected_range = None;
15485 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15486 let bytes_before_last_selection =
15487 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15488 let bytes_after_first_selection =
15489 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15490 let query_matches = query
15491 .stream_find_iter(bytes_before_last_selection)
15492 .map(|result| (last_selection.start, result))
15493 .chain(
15494 query
15495 .stream_find_iter(bytes_after_first_selection)
15496 .map(|result| (buffer.len(), result)),
15497 );
15498 for (end_offset, query_match) in query_matches {
15499 let query_match = query_match.unwrap(); // can only fail due to I/O
15500 let offset_range =
15501 end_offset - query_match.end()..end_offset - query_match.start();
15502
15503 if !select_prev_state.wordwise
15504 || (!buffer.is_inside_word(offset_range.start, None)
15505 && !buffer.is_inside_word(offset_range.end, None))
15506 {
15507 next_selected_range = Some(offset_range);
15508 break;
15509 }
15510 }
15511
15512 if let Some(next_selected_range) = next_selected_range {
15513 self.select_match_ranges(
15514 next_selected_range,
15515 last_selection.reversed,
15516 action.replace_newest,
15517 Some(Autoscroll::newest()),
15518 window,
15519 cx,
15520 );
15521 } else {
15522 select_prev_state.done = true;
15523 }
15524 }
15525
15526 self.select_prev_state = Some(select_prev_state);
15527 } else {
15528 let mut only_carets = true;
15529 let mut same_text_selected = true;
15530 let mut selected_text = None;
15531
15532 let mut selections_iter = selections.iter().peekable();
15533 while let Some(selection) = selections_iter.next() {
15534 if selection.start != selection.end {
15535 only_carets = false;
15536 }
15537
15538 if same_text_selected {
15539 if selected_text.is_none() {
15540 selected_text =
15541 Some(buffer.text_for_range(selection.range()).collect::<String>());
15542 }
15543
15544 if let Some(next_selection) = selections_iter.peek() {
15545 if next_selection.len() == selection.len() {
15546 let next_selected_text = buffer
15547 .text_for_range(next_selection.range())
15548 .collect::<String>();
15549 if Some(next_selected_text) != selected_text {
15550 same_text_selected = false;
15551 selected_text = None;
15552 }
15553 } else {
15554 same_text_selected = false;
15555 selected_text = None;
15556 }
15557 }
15558 }
15559 }
15560
15561 if only_carets {
15562 for selection in &mut selections {
15563 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15564 selection.start = word_range.start;
15565 selection.end = word_range.end;
15566 selection.goal = SelectionGoal::None;
15567 selection.reversed = false;
15568 self.select_match_ranges(
15569 selection.start..selection.end,
15570 selection.reversed,
15571 action.replace_newest,
15572 Some(Autoscroll::newest()),
15573 window,
15574 cx,
15575 );
15576 }
15577 if selections.len() == 1 {
15578 let selection = selections
15579 .last()
15580 .expect("ensured that there's only one selection");
15581 let query = buffer
15582 .text_for_range(selection.start..selection.end)
15583 .collect::<String>();
15584 let is_empty = query.is_empty();
15585 let select_state = SelectNextState {
15586 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15587 wordwise: true,
15588 done: is_empty,
15589 };
15590 self.select_prev_state = Some(select_state);
15591 } else {
15592 self.select_prev_state = None;
15593 }
15594 } else if let Some(selected_text) = selected_text {
15595 self.select_prev_state = Some(SelectNextState {
15596 query: self
15597 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15598 wordwise: false,
15599 done: false,
15600 });
15601 self.select_previous(action, window, cx)?;
15602 }
15603 }
15604 Ok(())
15605 }
15606
15607 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15608 /// setting the case sensitivity based on the global
15609 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15610 /// editor's settings.
15611 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15612 where
15613 I: IntoIterator<Item = P>,
15614 P: AsRef<[u8]>,
15615 {
15616 let case_sensitive = self
15617 .select_next_is_case_sensitive
15618 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15619
15620 let mut builder = AhoCorasickBuilder::new();
15621 builder.ascii_case_insensitive(!case_sensitive);
15622 builder.build(patterns)
15623 }
15624
15625 pub fn find_next_match(
15626 &mut self,
15627 _: &FindNextMatch,
15628 window: &mut Window,
15629 cx: &mut Context<Self>,
15630 ) -> Result<()> {
15631 let selections = self.selections.disjoint_anchors_arc();
15632 match selections.first() {
15633 Some(first) if selections.len() >= 2 => {
15634 self.change_selections(Default::default(), window, cx, |s| {
15635 s.select_ranges([first.range()]);
15636 });
15637 }
15638 _ => self.select_next(
15639 &SelectNext {
15640 replace_newest: true,
15641 },
15642 window,
15643 cx,
15644 )?,
15645 }
15646 Ok(())
15647 }
15648
15649 pub fn find_previous_match(
15650 &mut self,
15651 _: &FindPreviousMatch,
15652 window: &mut Window,
15653 cx: &mut Context<Self>,
15654 ) -> Result<()> {
15655 let selections = self.selections.disjoint_anchors_arc();
15656 match selections.last() {
15657 Some(last) if selections.len() >= 2 => {
15658 self.change_selections(Default::default(), window, cx, |s| {
15659 s.select_ranges([last.range()]);
15660 });
15661 }
15662 _ => self.select_previous(
15663 &SelectPrevious {
15664 replace_newest: true,
15665 },
15666 window,
15667 cx,
15668 )?,
15669 }
15670 Ok(())
15671 }
15672
15673 pub fn toggle_comments(
15674 &mut self,
15675 action: &ToggleComments,
15676 window: &mut Window,
15677 cx: &mut Context<Self>,
15678 ) {
15679 if self.read_only(cx) {
15680 return;
15681 }
15682 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15683 let text_layout_details = &self.text_layout_details(window);
15684 self.transact(window, cx, |this, window, cx| {
15685 let mut selections = this
15686 .selections
15687 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15688 let mut edits = Vec::new();
15689 let mut selection_edit_ranges = Vec::new();
15690 let mut last_toggled_row = None;
15691 let snapshot = this.buffer.read(cx).read(cx);
15692 let empty_str: Arc<str> = Arc::default();
15693 let mut suffixes_inserted = Vec::new();
15694 let ignore_indent = action.ignore_indent;
15695
15696 fn comment_prefix_range(
15697 snapshot: &MultiBufferSnapshot,
15698 row: MultiBufferRow,
15699 comment_prefix: &str,
15700 comment_prefix_whitespace: &str,
15701 ignore_indent: bool,
15702 ) -> Range<Point> {
15703 let indent_size = if ignore_indent {
15704 0
15705 } else {
15706 snapshot.indent_size_for_line(row).len
15707 };
15708
15709 let start = Point::new(row.0, indent_size);
15710
15711 let mut line_bytes = snapshot
15712 .bytes_in_range(start..snapshot.max_point())
15713 .flatten()
15714 .copied();
15715
15716 // If this line currently begins with the line comment prefix, then record
15717 // the range containing the prefix.
15718 if line_bytes
15719 .by_ref()
15720 .take(comment_prefix.len())
15721 .eq(comment_prefix.bytes())
15722 {
15723 // Include any whitespace that matches the comment prefix.
15724 let matching_whitespace_len = line_bytes
15725 .zip(comment_prefix_whitespace.bytes())
15726 .take_while(|(a, b)| a == b)
15727 .count() as u32;
15728 let end = Point::new(
15729 start.row,
15730 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15731 );
15732 start..end
15733 } else {
15734 start..start
15735 }
15736 }
15737
15738 fn comment_suffix_range(
15739 snapshot: &MultiBufferSnapshot,
15740 row: MultiBufferRow,
15741 comment_suffix: &str,
15742 comment_suffix_has_leading_space: bool,
15743 ) -> Range<Point> {
15744 let end = Point::new(row.0, snapshot.line_len(row));
15745 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15746
15747 let mut line_end_bytes = snapshot
15748 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15749 .flatten()
15750 .copied();
15751
15752 let leading_space_len = if suffix_start_column > 0
15753 && line_end_bytes.next() == Some(b' ')
15754 && comment_suffix_has_leading_space
15755 {
15756 1
15757 } else {
15758 0
15759 };
15760
15761 // If this line currently begins with the line comment prefix, then record
15762 // the range containing the prefix.
15763 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15764 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15765 start..end
15766 } else {
15767 end..end
15768 }
15769 }
15770
15771 // TODO: Handle selections that cross excerpts
15772 for selection in &mut selections {
15773 let start_column = snapshot
15774 .indent_size_for_line(MultiBufferRow(selection.start.row))
15775 .len;
15776 let language = if let Some(language) =
15777 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15778 {
15779 language
15780 } else {
15781 continue;
15782 };
15783
15784 selection_edit_ranges.clear();
15785
15786 // If multiple selections contain a given row, avoid processing that
15787 // row more than once.
15788 let mut start_row = MultiBufferRow(selection.start.row);
15789 if last_toggled_row == Some(start_row) {
15790 start_row = start_row.next_row();
15791 }
15792 let end_row =
15793 if selection.end.row > selection.start.row && selection.end.column == 0 {
15794 MultiBufferRow(selection.end.row - 1)
15795 } else {
15796 MultiBufferRow(selection.end.row)
15797 };
15798 last_toggled_row = Some(end_row);
15799
15800 if start_row > end_row {
15801 continue;
15802 }
15803
15804 // If the language has line comments, toggle those.
15805 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15806
15807 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15808 if ignore_indent {
15809 full_comment_prefixes = full_comment_prefixes
15810 .into_iter()
15811 .map(|s| Arc::from(s.trim_end()))
15812 .collect();
15813 }
15814
15815 if !full_comment_prefixes.is_empty() {
15816 let first_prefix = full_comment_prefixes
15817 .first()
15818 .expect("prefixes is non-empty");
15819 let prefix_trimmed_lengths = full_comment_prefixes
15820 .iter()
15821 .map(|p| p.trim_end_matches(' ').len())
15822 .collect::<SmallVec<[usize; 4]>>();
15823
15824 let mut all_selection_lines_are_comments = true;
15825
15826 for row in start_row.0..=end_row.0 {
15827 let row = MultiBufferRow(row);
15828 if start_row < end_row && snapshot.is_line_blank(row) {
15829 continue;
15830 }
15831
15832 let prefix_range = full_comment_prefixes
15833 .iter()
15834 .zip(prefix_trimmed_lengths.iter().copied())
15835 .map(|(prefix, trimmed_prefix_len)| {
15836 comment_prefix_range(
15837 snapshot.deref(),
15838 row,
15839 &prefix[..trimmed_prefix_len],
15840 &prefix[trimmed_prefix_len..],
15841 ignore_indent,
15842 )
15843 })
15844 .max_by_key(|range| range.end.column - range.start.column)
15845 .expect("prefixes is non-empty");
15846
15847 if prefix_range.is_empty() {
15848 all_selection_lines_are_comments = false;
15849 }
15850
15851 selection_edit_ranges.push(prefix_range);
15852 }
15853
15854 if all_selection_lines_are_comments {
15855 edits.extend(
15856 selection_edit_ranges
15857 .iter()
15858 .cloned()
15859 .map(|range| (range, empty_str.clone())),
15860 );
15861 } else {
15862 let min_column = selection_edit_ranges
15863 .iter()
15864 .map(|range| range.start.column)
15865 .min()
15866 .unwrap_or(0);
15867 edits.extend(selection_edit_ranges.iter().map(|range| {
15868 let position = Point::new(range.start.row, min_column);
15869 (position..position, first_prefix.clone())
15870 }));
15871 }
15872 } else if let Some(BlockCommentConfig {
15873 start: full_comment_prefix,
15874 end: comment_suffix,
15875 ..
15876 }) = language.block_comment()
15877 {
15878 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15879 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15880 let prefix_range = comment_prefix_range(
15881 snapshot.deref(),
15882 start_row,
15883 comment_prefix,
15884 comment_prefix_whitespace,
15885 ignore_indent,
15886 );
15887 let suffix_range = comment_suffix_range(
15888 snapshot.deref(),
15889 end_row,
15890 comment_suffix.trim_start_matches(' '),
15891 comment_suffix.starts_with(' '),
15892 );
15893
15894 if prefix_range.is_empty() || suffix_range.is_empty() {
15895 edits.push((
15896 prefix_range.start..prefix_range.start,
15897 full_comment_prefix.clone(),
15898 ));
15899 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15900 suffixes_inserted.push((end_row, comment_suffix.len()));
15901 } else {
15902 edits.push((prefix_range, empty_str.clone()));
15903 edits.push((suffix_range, empty_str.clone()));
15904 }
15905 } else {
15906 continue;
15907 }
15908 }
15909
15910 drop(snapshot);
15911 this.buffer.update(cx, |buffer, cx| {
15912 buffer.edit(edits, None, cx);
15913 });
15914
15915 // Adjust selections so that they end before any comment suffixes that
15916 // were inserted.
15917 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15918 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15919 let snapshot = this.buffer.read(cx).read(cx);
15920 for selection in &mut selections {
15921 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15922 match row.cmp(&MultiBufferRow(selection.end.row)) {
15923 Ordering::Less => {
15924 suffixes_inserted.next();
15925 continue;
15926 }
15927 Ordering::Greater => break,
15928 Ordering::Equal => {
15929 if selection.end.column == snapshot.line_len(row) {
15930 if selection.is_empty() {
15931 selection.start.column -= suffix_len as u32;
15932 }
15933 selection.end.column -= suffix_len as u32;
15934 }
15935 break;
15936 }
15937 }
15938 }
15939 }
15940
15941 drop(snapshot);
15942 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15943
15944 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15945 let selections_on_single_row = selections.windows(2).all(|selections| {
15946 selections[0].start.row == selections[1].start.row
15947 && selections[0].end.row == selections[1].end.row
15948 && selections[0].start.row == selections[0].end.row
15949 });
15950 let selections_selecting = selections
15951 .iter()
15952 .any(|selection| selection.start != selection.end);
15953 let advance_downwards = action.advance_downwards
15954 && selections_on_single_row
15955 && !selections_selecting
15956 && !matches!(this.mode, EditorMode::SingleLine);
15957
15958 if advance_downwards {
15959 let snapshot = this.buffer.read(cx).snapshot(cx);
15960
15961 this.change_selections(Default::default(), window, cx, |s| {
15962 s.move_cursors_with(|display_snapshot, display_point, _| {
15963 let mut point = display_point.to_point(display_snapshot);
15964 point.row += 1;
15965 point = snapshot.clip_point(point, Bias::Left);
15966 let display_point = point.to_display_point(display_snapshot);
15967 let goal = SelectionGoal::HorizontalPosition(
15968 display_snapshot
15969 .x_for_display_point(display_point, text_layout_details)
15970 .into(),
15971 );
15972 (display_point, goal)
15973 })
15974 });
15975 }
15976 });
15977 }
15978
15979 pub fn select_enclosing_symbol(
15980 &mut self,
15981 _: &SelectEnclosingSymbol,
15982 window: &mut Window,
15983 cx: &mut Context<Self>,
15984 ) {
15985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15986
15987 let buffer = self.buffer.read(cx).snapshot(cx);
15988 let old_selections = self
15989 .selections
15990 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15991 .into_boxed_slice();
15992
15993 fn update_selection(
15994 selection: &Selection<MultiBufferOffset>,
15995 buffer_snap: &MultiBufferSnapshot,
15996 ) -> Option<Selection<MultiBufferOffset>> {
15997 let cursor = selection.head();
15998 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15999 for symbol in symbols.iter().rev() {
16000 let start = symbol.range.start.to_offset(buffer_snap);
16001 let end = symbol.range.end.to_offset(buffer_snap);
16002 let new_range = start..end;
16003 if start < selection.start || end > selection.end {
16004 return Some(Selection {
16005 id: selection.id,
16006 start: new_range.start,
16007 end: new_range.end,
16008 goal: SelectionGoal::None,
16009 reversed: selection.reversed,
16010 });
16011 }
16012 }
16013 None
16014 }
16015
16016 let mut selected_larger_symbol = false;
16017 let new_selections = old_selections
16018 .iter()
16019 .map(|selection| match update_selection(selection, &buffer) {
16020 Some(new_selection) => {
16021 if new_selection.range() != selection.range() {
16022 selected_larger_symbol = true;
16023 }
16024 new_selection
16025 }
16026 None => selection.clone(),
16027 })
16028 .collect::<Vec<_>>();
16029
16030 if selected_larger_symbol {
16031 self.change_selections(Default::default(), window, cx, |s| {
16032 s.select(new_selections);
16033 });
16034 }
16035 }
16036
16037 pub fn select_larger_syntax_node(
16038 &mut self,
16039 _: &SelectLargerSyntaxNode,
16040 window: &mut Window,
16041 cx: &mut Context<Self>,
16042 ) {
16043 let Some(visible_row_count) = self.visible_row_count() else {
16044 return;
16045 };
16046 let old_selections: Box<[_]> = self
16047 .selections
16048 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16049 .into();
16050 if old_selections.is_empty() {
16051 return;
16052 }
16053
16054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16055
16056 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16057 let buffer = self.buffer.read(cx).snapshot(cx);
16058
16059 let mut selected_larger_node = false;
16060 let mut new_selections = old_selections
16061 .iter()
16062 .map(|selection| {
16063 let old_range = selection.start..selection.end;
16064
16065 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16066 // manually select word at selection
16067 if ["string_content", "inline"].contains(&node.kind()) {
16068 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16069 // ignore if word is already selected
16070 if !word_range.is_empty() && old_range != word_range {
16071 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16072 // only select word if start and end point belongs to same word
16073 if word_range == last_word_range {
16074 selected_larger_node = true;
16075 return Selection {
16076 id: selection.id,
16077 start: word_range.start,
16078 end: word_range.end,
16079 goal: SelectionGoal::None,
16080 reversed: selection.reversed,
16081 };
16082 }
16083 }
16084 }
16085 }
16086
16087 let mut new_range = old_range.clone();
16088 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16089 new_range = range;
16090 if !node.is_named() {
16091 continue;
16092 }
16093 if !display_map.intersects_fold(new_range.start)
16094 && !display_map.intersects_fold(new_range.end)
16095 {
16096 break;
16097 }
16098 }
16099
16100 selected_larger_node |= new_range != old_range;
16101 Selection {
16102 id: selection.id,
16103 start: new_range.start,
16104 end: new_range.end,
16105 goal: SelectionGoal::None,
16106 reversed: selection.reversed,
16107 }
16108 })
16109 .collect::<Vec<_>>();
16110
16111 if !selected_larger_node {
16112 return; // don't put this call in the history
16113 }
16114
16115 // scroll based on transformation done to the last selection created by the user
16116 let (last_old, last_new) = old_selections
16117 .last()
16118 .zip(new_selections.last().cloned())
16119 .expect("old_selections isn't empty");
16120
16121 // revert selection
16122 let is_selection_reversed = {
16123 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16124 new_selections.last_mut().expect("checked above").reversed =
16125 should_newest_selection_be_reversed;
16126 should_newest_selection_be_reversed
16127 };
16128
16129 if selected_larger_node {
16130 self.select_syntax_node_history.disable_clearing = true;
16131 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16132 s.select(new_selections.clone());
16133 });
16134 self.select_syntax_node_history.disable_clearing = false;
16135 }
16136
16137 let start_row = last_new.start.to_display_point(&display_map).row().0;
16138 let end_row = last_new.end.to_display_point(&display_map).row().0;
16139 let selection_height = end_row - start_row + 1;
16140 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16141
16142 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16143 let scroll_behavior = if fits_on_the_screen {
16144 self.request_autoscroll(Autoscroll::fit(), cx);
16145 SelectSyntaxNodeScrollBehavior::FitSelection
16146 } else if is_selection_reversed {
16147 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16148 SelectSyntaxNodeScrollBehavior::CursorTop
16149 } else {
16150 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16151 SelectSyntaxNodeScrollBehavior::CursorBottom
16152 };
16153
16154 self.select_syntax_node_history.push((
16155 old_selections,
16156 scroll_behavior,
16157 is_selection_reversed,
16158 ));
16159 }
16160
16161 pub fn select_smaller_syntax_node(
16162 &mut self,
16163 _: &SelectSmallerSyntaxNode,
16164 window: &mut Window,
16165 cx: &mut Context<Self>,
16166 ) {
16167 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16168
16169 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16170 self.select_syntax_node_history.pop()
16171 {
16172 if let Some(selection) = selections.last_mut() {
16173 selection.reversed = is_selection_reversed;
16174 }
16175
16176 self.select_syntax_node_history.disable_clearing = true;
16177 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16178 s.select(selections.to_vec());
16179 });
16180 self.select_syntax_node_history.disable_clearing = false;
16181
16182 match scroll_behavior {
16183 SelectSyntaxNodeScrollBehavior::CursorTop => {
16184 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16185 }
16186 SelectSyntaxNodeScrollBehavior::FitSelection => {
16187 self.request_autoscroll(Autoscroll::fit(), cx);
16188 }
16189 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16190 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16191 }
16192 }
16193 }
16194 }
16195
16196 pub fn unwrap_syntax_node(
16197 &mut self,
16198 _: &UnwrapSyntaxNode,
16199 window: &mut Window,
16200 cx: &mut Context<Self>,
16201 ) {
16202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16203
16204 let buffer = self.buffer.read(cx).snapshot(cx);
16205 let selections = self
16206 .selections
16207 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16208 .into_iter()
16209 // subtracting the offset requires sorting
16210 .sorted_by_key(|i| i.start);
16211
16212 let full_edits = selections
16213 .into_iter()
16214 .filter_map(|selection| {
16215 let child = if selection.is_empty()
16216 && let Some((_, ancestor_range)) =
16217 buffer.syntax_ancestor(selection.start..selection.end)
16218 {
16219 ancestor_range
16220 } else {
16221 selection.range()
16222 };
16223
16224 let mut parent = child.clone();
16225 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16226 parent = ancestor_range;
16227 if parent.start < child.start || parent.end > child.end {
16228 break;
16229 }
16230 }
16231
16232 if parent == child {
16233 return None;
16234 }
16235 let text = buffer.text_for_range(child).collect::<String>();
16236 Some((selection.id, parent, text))
16237 })
16238 .collect::<Vec<_>>();
16239 if full_edits.is_empty() {
16240 return;
16241 }
16242
16243 self.transact(window, cx, |this, window, cx| {
16244 this.buffer.update(cx, |buffer, cx| {
16245 buffer.edit(
16246 full_edits
16247 .iter()
16248 .map(|(_, p, t)| (p.clone(), t.clone()))
16249 .collect::<Vec<_>>(),
16250 None,
16251 cx,
16252 );
16253 });
16254 this.change_selections(Default::default(), window, cx, |s| {
16255 let mut offset = 0;
16256 let mut selections = vec![];
16257 for (id, parent, text) in full_edits {
16258 let start = parent.start - offset;
16259 offset += (parent.end - parent.start) - text.len();
16260 selections.push(Selection {
16261 id,
16262 start,
16263 end: start + text.len(),
16264 reversed: false,
16265 goal: Default::default(),
16266 });
16267 }
16268 s.select(selections);
16269 });
16270 });
16271 }
16272
16273 pub fn select_next_syntax_node(
16274 &mut self,
16275 _: &SelectNextSyntaxNode,
16276 window: &mut Window,
16277 cx: &mut Context<Self>,
16278 ) {
16279 let old_selections: Box<[_]> = self
16280 .selections
16281 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16282 .into();
16283 if old_selections.is_empty() {
16284 return;
16285 }
16286
16287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16288
16289 let buffer = self.buffer.read(cx).snapshot(cx);
16290 let mut selected_sibling = false;
16291
16292 let new_selections = old_selections
16293 .iter()
16294 .map(|selection| {
16295 let old_range = selection.start..selection.end;
16296
16297 let old_range =
16298 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16299 let excerpt = buffer.excerpt_containing(old_range.clone());
16300
16301 if let Some(mut excerpt) = excerpt
16302 && let Some(node) = excerpt
16303 .buffer()
16304 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16305 {
16306 let new_range = excerpt.map_range_from_buffer(
16307 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16308 );
16309 selected_sibling = true;
16310 Selection {
16311 id: selection.id,
16312 start: new_range.start,
16313 end: new_range.end,
16314 goal: SelectionGoal::None,
16315 reversed: selection.reversed,
16316 }
16317 } else {
16318 selection.clone()
16319 }
16320 })
16321 .collect::<Vec<_>>();
16322
16323 if selected_sibling {
16324 self.change_selections(
16325 SelectionEffects::scroll(Autoscroll::fit()),
16326 window,
16327 cx,
16328 |s| {
16329 s.select(new_selections);
16330 },
16331 );
16332 }
16333 }
16334
16335 pub fn select_prev_syntax_node(
16336 &mut self,
16337 _: &SelectPreviousSyntaxNode,
16338 window: &mut Window,
16339 cx: &mut Context<Self>,
16340 ) {
16341 let old_selections: Box<[_]> = self
16342 .selections
16343 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16344 .into();
16345 if old_selections.is_empty() {
16346 return;
16347 }
16348
16349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16350
16351 let buffer = self.buffer.read(cx).snapshot(cx);
16352 let mut selected_sibling = false;
16353
16354 let new_selections = old_selections
16355 .iter()
16356 .map(|selection| {
16357 let old_range = selection.start..selection.end;
16358 let old_range =
16359 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16360 let excerpt = buffer.excerpt_containing(old_range.clone());
16361
16362 if let Some(mut excerpt) = excerpt
16363 && let Some(node) = excerpt
16364 .buffer()
16365 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16366 {
16367 let new_range = excerpt.map_range_from_buffer(
16368 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16369 );
16370 selected_sibling = true;
16371 Selection {
16372 id: selection.id,
16373 start: new_range.start,
16374 end: new_range.end,
16375 goal: SelectionGoal::None,
16376 reversed: selection.reversed,
16377 }
16378 } else {
16379 selection.clone()
16380 }
16381 })
16382 .collect::<Vec<_>>();
16383
16384 if selected_sibling {
16385 self.change_selections(
16386 SelectionEffects::scroll(Autoscroll::fit()),
16387 window,
16388 cx,
16389 |s| {
16390 s.select(new_selections);
16391 },
16392 );
16393 }
16394 }
16395
16396 pub fn move_to_start_of_larger_syntax_node(
16397 &mut self,
16398 _: &MoveToStartOfLargerSyntaxNode,
16399 window: &mut Window,
16400 cx: &mut Context<Self>,
16401 ) {
16402 self.move_cursors_to_syntax_nodes(window, cx, false);
16403 }
16404
16405 pub fn move_to_end_of_larger_syntax_node(
16406 &mut self,
16407 _: &MoveToEndOfLargerSyntaxNode,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) {
16411 self.move_cursors_to_syntax_nodes(window, cx, true);
16412 }
16413
16414 fn move_cursors_to_syntax_nodes(
16415 &mut self,
16416 window: &mut Window,
16417 cx: &mut Context<Self>,
16418 move_to_end: bool,
16419 ) -> bool {
16420 let old_selections: Box<[_]> = self
16421 .selections
16422 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16423 .into();
16424 if old_selections.is_empty() {
16425 return false;
16426 }
16427
16428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16429
16430 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16431 let buffer = self.buffer.read(cx).snapshot(cx);
16432
16433 let mut any_cursor_moved = false;
16434 let new_selections = old_selections
16435 .iter()
16436 .map(|selection| {
16437 if !selection.is_empty() {
16438 return selection.clone();
16439 }
16440
16441 let selection_pos = selection.head();
16442 let old_range = selection_pos..selection_pos;
16443
16444 let mut new_pos = selection_pos;
16445 let mut search_range = old_range;
16446 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16447 search_range = range.clone();
16448 if !node.is_named()
16449 || display_map.intersects_fold(range.start)
16450 || display_map.intersects_fold(range.end)
16451 // If cursor is already at the end of the syntax node, continue searching
16452 || (move_to_end && range.end == selection_pos)
16453 // If cursor is already at the start of the syntax node, continue searching
16454 || (!move_to_end && range.start == selection_pos)
16455 {
16456 continue;
16457 }
16458
16459 // If we found a string_content node, find the largest parent that is still string_content
16460 // Enables us to skip to the end of strings without taking multiple steps inside the string
16461 let (_, final_range) = if node.kind() == "string_content" {
16462 let mut current_node = node;
16463 let mut current_range = range;
16464 while let Some((parent, parent_range)) =
16465 buffer.syntax_ancestor(current_range.clone())
16466 {
16467 if parent.kind() == "string_content" {
16468 current_node = parent;
16469 current_range = parent_range;
16470 } else {
16471 break;
16472 }
16473 }
16474
16475 (current_node, current_range)
16476 } else {
16477 (node, range)
16478 };
16479
16480 new_pos = if move_to_end {
16481 final_range.end
16482 } else {
16483 final_range.start
16484 };
16485
16486 break;
16487 }
16488
16489 any_cursor_moved |= new_pos != selection_pos;
16490
16491 Selection {
16492 id: selection.id,
16493 start: new_pos,
16494 end: new_pos,
16495 goal: SelectionGoal::None,
16496 reversed: false,
16497 }
16498 })
16499 .collect::<Vec<_>>();
16500
16501 self.change_selections(Default::default(), window, cx, |s| {
16502 s.select(new_selections);
16503 });
16504 self.request_autoscroll(Autoscroll::newest(), cx);
16505
16506 any_cursor_moved
16507 }
16508
16509 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16510 if !EditorSettings::get_global(cx).gutter.runnables {
16511 self.clear_tasks();
16512 return Task::ready(());
16513 }
16514 let project = self.project().map(Entity::downgrade);
16515 let task_sources = self.lsp_task_sources(cx);
16516 let multi_buffer = self.buffer.downgrade();
16517 cx.spawn_in(window, async move |editor, cx| {
16518 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16519 let Some(project) = project.and_then(|p| p.upgrade()) else {
16520 return;
16521 };
16522 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16523 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16524 }) else {
16525 return;
16526 };
16527
16528 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16529 if hide_runnables {
16530 return;
16531 }
16532 let new_rows =
16533 cx.background_spawn({
16534 let snapshot = display_snapshot.clone();
16535 async move {
16536 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16537 }
16538 })
16539 .await;
16540 let Ok(lsp_tasks) =
16541 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16542 else {
16543 return;
16544 };
16545 let lsp_tasks = lsp_tasks.await;
16546
16547 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16548 lsp_tasks
16549 .into_iter()
16550 .flat_map(|(kind, tasks)| {
16551 tasks.into_iter().filter_map(move |(location, task)| {
16552 Some((kind.clone(), location?, task))
16553 })
16554 })
16555 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16556 let buffer = location.target.buffer;
16557 let buffer_snapshot = buffer.read(cx).snapshot();
16558 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16559 |(excerpt_id, snapshot, _)| {
16560 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16561 display_snapshot
16562 .buffer_snapshot()
16563 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16564 } else {
16565 None
16566 }
16567 },
16568 );
16569 if let Some(offset) = offset {
16570 let task_buffer_range =
16571 location.target.range.to_point(&buffer_snapshot);
16572 let context_buffer_range =
16573 task_buffer_range.to_offset(&buffer_snapshot);
16574 let context_range = BufferOffset(context_buffer_range.start)
16575 ..BufferOffset(context_buffer_range.end);
16576
16577 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16578 .or_insert_with(|| RunnableTasks {
16579 templates: Vec::new(),
16580 offset,
16581 column: task_buffer_range.start.column,
16582 extra_variables: HashMap::default(),
16583 context_range,
16584 })
16585 .templates
16586 .push((kind, task.original_task().clone()));
16587 }
16588
16589 acc
16590 })
16591 }) else {
16592 return;
16593 };
16594
16595 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16596 buffer.language_settings(cx).tasks.prefer_lsp
16597 }) else {
16598 return;
16599 };
16600
16601 let rows = Self::runnable_rows(
16602 project,
16603 display_snapshot,
16604 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16605 new_rows,
16606 cx.clone(),
16607 )
16608 .await;
16609 editor
16610 .update(cx, |editor, _| {
16611 editor.clear_tasks();
16612 for (key, mut value) in rows {
16613 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16614 value.templates.extend(lsp_tasks.templates);
16615 }
16616
16617 editor.insert_tasks(key, value);
16618 }
16619 for (key, value) in lsp_tasks_by_rows {
16620 editor.insert_tasks(key, value);
16621 }
16622 })
16623 .ok();
16624 })
16625 }
16626 fn fetch_runnable_ranges(
16627 snapshot: &DisplaySnapshot,
16628 range: Range<Anchor>,
16629 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16630 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16631 }
16632
16633 fn runnable_rows(
16634 project: Entity<Project>,
16635 snapshot: DisplaySnapshot,
16636 prefer_lsp: bool,
16637 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16638 cx: AsyncWindowContext,
16639 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16640 cx.spawn(async move |cx| {
16641 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16642 for (run_range, mut runnable) in runnable_ranges {
16643 let Some(tasks) = cx
16644 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16645 .ok()
16646 else {
16647 continue;
16648 };
16649 let mut tasks = tasks.await;
16650
16651 if prefer_lsp {
16652 tasks.retain(|(task_kind, _)| {
16653 !matches!(task_kind, TaskSourceKind::Language { .. })
16654 });
16655 }
16656 if tasks.is_empty() {
16657 continue;
16658 }
16659
16660 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16661 let Some(row) = snapshot
16662 .buffer_snapshot()
16663 .buffer_line_for_row(MultiBufferRow(point.row))
16664 .map(|(_, range)| range.start.row)
16665 else {
16666 continue;
16667 };
16668
16669 let context_range =
16670 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16671 runnable_rows.push((
16672 (runnable.buffer_id, row),
16673 RunnableTasks {
16674 templates: tasks,
16675 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16676 context_range,
16677 column: point.column,
16678 extra_variables: runnable.extra_captures,
16679 },
16680 ));
16681 }
16682 runnable_rows
16683 })
16684 }
16685
16686 fn templates_with_tags(
16687 project: &Entity<Project>,
16688 runnable: &mut Runnable,
16689 cx: &mut App,
16690 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16691 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16692 let (worktree_id, file) = project
16693 .buffer_for_id(runnable.buffer, cx)
16694 .and_then(|buffer| buffer.read(cx).file())
16695 .map(|file| (file.worktree_id(cx), file.clone()))
16696 .unzip();
16697
16698 (
16699 project.task_store().read(cx).task_inventory().cloned(),
16700 worktree_id,
16701 file,
16702 )
16703 });
16704
16705 let tags = mem::take(&mut runnable.tags);
16706 let language = runnable.language.clone();
16707 cx.spawn(async move |cx| {
16708 let mut templates_with_tags = Vec::new();
16709 if let Some(inventory) = inventory {
16710 for RunnableTag(tag) in tags {
16711 let new_tasks = inventory.update(cx, |inventory, cx| {
16712 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16713 });
16714 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16715 move |(_, template)| {
16716 template.tags.iter().any(|source_tag| source_tag == &tag)
16717 },
16718 ));
16719 }
16720 }
16721 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16722
16723 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16724 // Strongest source wins; if we have worktree tag binding, prefer that to
16725 // global and language bindings;
16726 // if we have a global binding, prefer that to language binding.
16727 let first_mismatch = templates_with_tags
16728 .iter()
16729 .position(|(tag_source, _)| tag_source != leading_tag_source);
16730 if let Some(index) = first_mismatch {
16731 templates_with_tags.truncate(index);
16732 }
16733 }
16734
16735 templates_with_tags
16736 })
16737 }
16738
16739 pub fn move_to_enclosing_bracket(
16740 &mut self,
16741 _: &MoveToEnclosingBracket,
16742 window: &mut Window,
16743 cx: &mut Context<Self>,
16744 ) {
16745 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16746 self.change_selections(Default::default(), window, cx, |s| {
16747 s.move_offsets_with(|snapshot, selection| {
16748 let Some(enclosing_bracket_ranges) =
16749 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16750 else {
16751 return;
16752 };
16753
16754 let mut best_length = usize::MAX;
16755 let mut best_inside = false;
16756 let mut best_in_bracket_range = false;
16757 let mut best_destination = None;
16758 for (open, close) in enclosing_bracket_ranges {
16759 let close = close.to_inclusive();
16760 let length = *close.end() - open.start;
16761 let inside = selection.start >= open.end && selection.end <= *close.start();
16762 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16763 || close.contains(&selection.head());
16764
16765 // If best is next to a bracket and current isn't, skip
16766 if !in_bracket_range && best_in_bracket_range {
16767 continue;
16768 }
16769
16770 // Prefer smaller lengths unless best is inside and current isn't
16771 if length > best_length && (best_inside || !inside) {
16772 continue;
16773 }
16774
16775 best_length = length;
16776 best_inside = inside;
16777 best_in_bracket_range = in_bracket_range;
16778 best_destination = Some(
16779 if close.contains(&selection.start) && close.contains(&selection.end) {
16780 if inside { open.end } else { open.start }
16781 } else if inside {
16782 *close.start()
16783 } else {
16784 *close.end()
16785 },
16786 );
16787 }
16788
16789 if let Some(destination) = best_destination {
16790 selection.collapse_to(destination, SelectionGoal::None);
16791 }
16792 })
16793 });
16794 }
16795
16796 pub fn undo_selection(
16797 &mut self,
16798 _: &UndoSelection,
16799 window: &mut Window,
16800 cx: &mut Context<Self>,
16801 ) {
16802 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16803 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16804 self.selection_history.mode = SelectionHistoryMode::Undoing;
16805 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16806 this.end_selection(window, cx);
16807 this.change_selections(
16808 SelectionEffects::scroll(Autoscroll::newest()),
16809 window,
16810 cx,
16811 |s| s.select_anchors(entry.selections.to_vec()),
16812 );
16813 });
16814 self.selection_history.mode = SelectionHistoryMode::Normal;
16815
16816 self.select_next_state = entry.select_next_state;
16817 self.select_prev_state = entry.select_prev_state;
16818 self.add_selections_state = entry.add_selections_state;
16819 }
16820 }
16821
16822 pub fn redo_selection(
16823 &mut self,
16824 _: &RedoSelection,
16825 window: &mut Window,
16826 cx: &mut Context<Self>,
16827 ) {
16828 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16829 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16830 self.selection_history.mode = SelectionHistoryMode::Redoing;
16831 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16832 this.end_selection(window, cx);
16833 this.change_selections(
16834 SelectionEffects::scroll(Autoscroll::newest()),
16835 window,
16836 cx,
16837 |s| s.select_anchors(entry.selections.to_vec()),
16838 );
16839 });
16840 self.selection_history.mode = SelectionHistoryMode::Normal;
16841
16842 self.select_next_state = entry.select_next_state;
16843 self.select_prev_state = entry.select_prev_state;
16844 self.add_selections_state = entry.add_selections_state;
16845 }
16846 }
16847
16848 pub fn expand_excerpts(
16849 &mut self,
16850 action: &ExpandExcerpts,
16851 _: &mut Window,
16852 cx: &mut Context<Self>,
16853 ) {
16854 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16855 }
16856
16857 pub fn expand_excerpts_down(
16858 &mut self,
16859 action: &ExpandExcerptsDown,
16860 _: &mut Window,
16861 cx: &mut Context<Self>,
16862 ) {
16863 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16864 }
16865
16866 pub fn expand_excerpts_up(
16867 &mut self,
16868 action: &ExpandExcerptsUp,
16869 _: &mut Window,
16870 cx: &mut Context<Self>,
16871 ) {
16872 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16873 }
16874
16875 pub fn expand_excerpts_for_direction(
16876 &mut self,
16877 lines: u32,
16878 direction: ExpandExcerptDirection,
16879 cx: &mut Context<Self>,
16880 ) {
16881 let selections = self.selections.disjoint_anchors_arc();
16882
16883 let lines = if lines == 0 {
16884 EditorSettings::get_global(cx).expand_excerpt_lines
16885 } else {
16886 lines
16887 };
16888
16889 let snapshot = self.buffer.read(cx).snapshot(cx);
16890 let mut excerpt_ids = selections
16891 .iter()
16892 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16893 .collect::<Vec<_>>();
16894 excerpt_ids.sort();
16895 excerpt_ids.dedup();
16896
16897 if self.delegate_expand_excerpts {
16898 cx.emit(EditorEvent::ExpandExcerptsRequested {
16899 excerpt_ids,
16900 lines,
16901 direction,
16902 });
16903 return;
16904 }
16905
16906 self.buffer.update(cx, |buffer, cx| {
16907 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16908 })
16909 }
16910
16911 pub fn expand_excerpt(
16912 &mut self,
16913 excerpt: ExcerptId,
16914 direction: ExpandExcerptDirection,
16915 window: &mut Window,
16916 cx: &mut Context<Self>,
16917 ) {
16918 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16919
16920 if self.delegate_expand_excerpts {
16921 cx.emit(EditorEvent::ExpandExcerptsRequested {
16922 excerpt_ids: vec![excerpt],
16923 lines: lines_to_expand,
16924 direction,
16925 });
16926 return;
16927 }
16928
16929 let current_scroll_position = self.scroll_position(cx);
16930 let mut scroll = None;
16931
16932 if direction == ExpandExcerptDirection::Down {
16933 let multi_buffer = self.buffer.read(cx);
16934 let snapshot = multi_buffer.snapshot(cx);
16935 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16936 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16937 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16938 {
16939 let buffer_snapshot = buffer.read(cx).snapshot();
16940 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16941 let last_row = buffer_snapshot.max_point().row;
16942 let lines_below = last_row.saturating_sub(excerpt_end_row);
16943 if lines_below >= lines_to_expand {
16944 scroll = Some(
16945 current_scroll_position
16946 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16947 );
16948 }
16949 }
16950 }
16951 if direction == ExpandExcerptDirection::Up
16952 && self
16953 .buffer
16954 .read(cx)
16955 .snapshot(cx)
16956 .excerpt_before(excerpt)
16957 .is_none()
16958 {
16959 scroll = Some(current_scroll_position);
16960 }
16961
16962 self.buffer.update(cx, |buffer, cx| {
16963 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16964 });
16965
16966 if let Some(new_scroll_position) = scroll {
16967 self.set_scroll_position(new_scroll_position, window, cx);
16968 }
16969 }
16970
16971 pub fn go_to_singleton_buffer_point(
16972 &mut self,
16973 point: Point,
16974 window: &mut Window,
16975 cx: &mut Context<Self>,
16976 ) {
16977 self.go_to_singleton_buffer_range(point..point, window, cx);
16978 }
16979
16980 pub fn go_to_singleton_buffer_range(
16981 &mut self,
16982 range: Range<Point>,
16983 window: &mut Window,
16984 cx: &mut Context<Self>,
16985 ) {
16986 let multibuffer = self.buffer().read(cx);
16987 let Some(buffer) = multibuffer.as_singleton() else {
16988 return;
16989 };
16990 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16991 return;
16992 };
16993 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16994 return;
16995 };
16996 self.change_selections(
16997 SelectionEffects::default().nav_history(true),
16998 window,
16999 cx,
17000 |s| s.select_anchor_ranges([start..end]),
17001 );
17002 }
17003
17004 pub fn go_to_diagnostic(
17005 &mut self,
17006 action: &GoToDiagnostic,
17007 window: &mut Window,
17008 cx: &mut Context<Self>,
17009 ) {
17010 if !self.diagnostics_enabled() {
17011 return;
17012 }
17013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17014 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17015 }
17016
17017 pub fn go_to_prev_diagnostic(
17018 &mut self,
17019 action: &GoToPreviousDiagnostic,
17020 window: &mut Window,
17021 cx: &mut Context<Self>,
17022 ) {
17023 if !self.diagnostics_enabled() {
17024 return;
17025 }
17026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17027 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17028 }
17029
17030 pub fn go_to_diagnostic_impl(
17031 &mut self,
17032 direction: Direction,
17033 severity: GoToDiagnosticSeverityFilter,
17034 window: &mut Window,
17035 cx: &mut Context<Self>,
17036 ) {
17037 let buffer = self.buffer.read(cx).snapshot(cx);
17038 let selection = self
17039 .selections
17040 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17041
17042 let mut active_group_id = None;
17043 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17044 && active_group.active_range.start.to_offset(&buffer) == selection.start
17045 {
17046 active_group_id = Some(active_group.group_id);
17047 }
17048
17049 fn filtered<'a>(
17050 severity: GoToDiagnosticSeverityFilter,
17051 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17052 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17053 diagnostics
17054 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17055 .filter(|entry| entry.range.start != entry.range.end)
17056 .filter(|entry| !entry.diagnostic.is_unnecessary)
17057 }
17058
17059 let before = filtered(
17060 severity,
17061 buffer
17062 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17063 .filter(|entry| entry.range.start <= selection.start),
17064 );
17065 let after = filtered(
17066 severity,
17067 buffer
17068 .diagnostics_in_range(selection.start..buffer.len())
17069 .filter(|entry| entry.range.start >= selection.start),
17070 );
17071
17072 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17073 if direction == Direction::Prev {
17074 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17075 {
17076 for diagnostic in prev_diagnostics.into_iter().rev() {
17077 if diagnostic.range.start != selection.start
17078 || active_group_id
17079 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17080 {
17081 found = Some(diagnostic);
17082 break 'outer;
17083 }
17084 }
17085 }
17086 } else {
17087 for diagnostic in after.chain(before) {
17088 if diagnostic.range.start != selection.start
17089 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17090 {
17091 found = Some(diagnostic);
17092 break;
17093 }
17094 }
17095 }
17096 let Some(next_diagnostic) = found else {
17097 return;
17098 };
17099
17100 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17101 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17102 return;
17103 };
17104 let snapshot = self.snapshot(window, cx);
17105 if snapshot.intersects_fold(next_diagnostic.range.start) {
17106 self.unfold_ranges(
17107 std::slice::from_ref(&next_diagnostic.range),
17108 true,
17109 false,
17110 cx,
17111 );
17112 }
17113 self.change_selections(Default::default(), window, cx, |s| {
17114 s.select_ranges(vec![
17115 next_diagnostic.range.start..next_diagnostic.range.start,
17116 ])
17117 });
17118 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17119 self.refresh_edit_prediction(false, true, window, cx);
17120 }
17121
17122 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17124 let snapshot = self.snapshot(window, cx);
17125 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17126 self.go_to_hunk_before_or_after_position(
17127 &snapshot,
17128 selection.head(),
17129 Direction::Next,
17130 window,
17131 cx,
17132 );
17133 }
17134
17135 pub fn go_to_hunk_before_or_after_position(
17136 &mut self,
17137 snapshot: &EditorSnapshot,
17138 position: Point,
17139 direction: Direction,
17140 window: &mut Window,
17141 cx: &mut Context<Editor>,
17142 ) {
17143 let row = if direction == Direction::Next {
17144 self.hunk_after_position(snapshot, position)
17145 .map(|hunk| hunk.row_range.start)
17146 } else {
17147 self.hunk_before_position(snapshot, position)
17148 };
17149
17150 if let Some(row) = row {
17151 let destination = Point::new(row.0, 0);
17152 let autoscroll = Autoscroll::center();
17153
17154 self.unfold_ranges(&[destination..destination], false, false, cx);
17155 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17156 s.select_ranges([destination..destination]);
17157 });
17158 }
17159 }
17160
17161 fn hunk_after_position(
17162 &mut self,
17163 snapshot: &EditorSnapshot,
17164 position: Point,
17165 ) -> Option<MultiBufferDiffHunk> {
17166 snapshot
17167 .buffer_snapshot()
17168 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17169 .find(|hunk| hunk.row_range.start.0 > position.row)
17170 .or_else(|| {
17171 snapshot
17172 .buffer_snapshot()
17173 .diff_hunks_in_range(Point::zero()..position)
17174 .find(|hunk| hunk.row_range.end.0 < position.row)
17175 })
17176 }
17177
17178 fn go_to_prev_hunk(
17179 &mut self,
17180 _: &GoToPreviousHunk,
17181 window: &mut Window,
17182 cx: &mut Context<Self>,
17183 ) {
17184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17185 let snapshot = self.snapshot(window, cx);
17186 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17187 self.go_to_hunk_before_or_after_position(
17188 &snapshot,
17189 selection.head(),
17190 Direction::Prev,
17191 window,
17192 cx,
17193 );
17194 }
17195
17196 fn hunk_before_position(
17197 &mut self,
17198 snapshot: &EditorSnapshot,
17199 position: Point,
17200 ) -> Option<MultiBufferRow> {
17201 snapshot
17202 .buffer_snapshot()
17203 .diff_hunk_before(position)
17204 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17205 }
17206
17207 fn go_to_next_change(
17208 &mut self,
17209 _: &GoToNextChange,
17210 window: &mut Window,
17211 cx: &mut Context<Self>,
17212 ) {
17213 if let Some(selections) = self
17214 .change_list
17215 .next_change(1, Direction::Next)
17216 .map(|s| s.to_vec())
17217 {
17218 self.change_selections(Default::default(), window, cx, |s| {
17219 let map = s.display_snapshot();
17220 s.select_display_ranges(selections.iter().map(|a| {
17221 let point = a.to_display_point(&map);
17222 point..point
17223 }))
17224 })
17225 }
17226 }
17227
17228 fn go_to_previous_change(
17229 &mut self,
17230 _: &GoToPreviousChange,
17231 window: &mut Window,
17232 cx: &mut Context<Self>,
17233 ) {
17234 if let Some(selections) = self
17235 .change_list
17236 .next_change(1, Direction::Prev)
17237 .map(|s| s.to_vec())
17238 {
17239 self.change_selections(Default::default(), window, cx, |s| {
17240 let map = s.display_snapshot();
17241 s.select_display_ranges(selections.iter().map(|a| {
17242 let point = a.to_display_point(&map);
17243 point..point
17244 }))
17245 })
17246 }
17247 }
17248
17249 pub fn go_to_next_document_highlight(
17250 &mut self,
17251 _: &GoToNextDocumentHighlight,
17252 window: &mut Window,
17253 cx: &mut Context<Self>,
17254 ) {
17255 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17256 }
17257
17258 pub fn go_to_prev_document_highlight(
17259 &mut self,
17260 _: &GoToPreviousDocumentHighlight,
17261 window: &mut Window,
17262 cx: &mut Context<Self>,
17263 ) {
17264 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17265 }
17266
17267 pub fn go_to_document_highlight_before_or_after_position(
17268 &mut self,
17269 direction: Direction,
17270 window: &mut Window,
17271 cx: &mut Context<Editor>,
17272 ) {
17273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17274 let snapshot = self.snapshot(window, cx);
17275 let buffer = &snapshot.buffer_snapshot();
17276 let position = self
17277 .selections
17278 .newest::<Point>(&snapshot.display_snapshot)
17279 .head();
17280 let anchor_position = buffer.anchor_after(position);
17281
17282 // Get all document highlights (both read and write)
17283 let mut all_highlights = Vec::new();
17284
17285 if let Some((_, read_highlights)) = self
17286 .background_highlights
17287 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17288 {
17289 all_highlights.extend(read_highlights.iter());
17290 }
17291
17292 if let Some((_, write_highlights)) = self
17293 .background_highlights
17294 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17295 {
17296 all_highlights.extend(write_highlights.iter());
17297 }
17298
17299 if all_highlights.is_empty() {
17300 return;
17301 }
17302
17303 // Sort highlights by position
17304 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17305
17306 let target_highlight = match direction {
17307 Direction::Next => {
17308 // Find the first highlight after the current position
17309 all_highlights
17310 .iter()
17311 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17312 }
17313 Direction::Prev => {
17314 // Find the last highlight before the current position
17315 all_highlights
17316 .iter()
17317 .rev()
17318 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17319 }
17320 };
17321
17322 if let Some(highlight) = target_highlight {
17323 let destination = highlight.start.to_point(buffer);
17324 let autoscroll = Autoscroll::center();
17325
17326 self.unfold_ranges(&[destination..destination], false, false, cx);
17327 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17328 s.select_ranges([destination..destination]);
17329 });
17330 }
17331 }
17332
17333 fn go_to_line<T: 'static>(
17334 &mut self,
17335 position: Anchor,
17336 highlight_color: Option<Hsla>,
17337 window: &mut Window,
17338 cx: &mut Context<Self>,
17339 ) {
17340 let snapshot = self.snapshot(window, cx).display_snapshot;
17341 let position = position.to_point(&snapshot.buffer_snapshot());
17342 let start = snapshot
17343 .buffer_snapshot()
17344 .clip_point(Point::new(position.row, 0), Bias::Left);
17345 let end = start + Point::new(1, 0);
17346 let start = snapshot.buffer_snapshot().anchor_before(start);
17347 let end = snapshot.buffer_snapshot().anchor_before(end);
17348
17349 self.highlight_rows::<T>(
17350 start..end,
17351 highlight_color
17352 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17353 Default::default(),
17354 cx,
17355 );
17356
17357 if self.buffer.read(cx).is_singleton() {
17358 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17359 }
17360 }
17361
17362 pub fn go_to_definition(
17363 &mut self,
17364 _: &GoToDefinition,
17365 window: &mut Window,
17366 cx: &mut Context<Self>,
17367 ) -> Task<Result<Navigated>> {
17368 let definition =
17369 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17370 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17371 cx.spawn_in(window, async move |editor, cx| {
17372 if definition.await? == Navigated::Yes {
17373 return Ok(Navigated::Yes);
17374 }
17375 match fallback_strategy {
17376 GoToDefinitionFallback::None => Ok(Navigated::No),
17377 GoToDefinitionFallback::FindAllReferences => {
17378 match editor.update_in(cx, |editor, window, cx| {
17379 editor.find_all_references(&FindAllReferences::default(), window, cx)
17380 })? {
17381 Some(references) => references.await,
17382 None => Ok(Navigated::No),
17383 }
17384 }
17385 }
17386 })
17387 }
17388
17389 pub fn go_to_declaration(
17390 &mut self,
17391 _: &GoToDeclaration,
17392 window: &mut Window,
17393 cx: &mut Context<Self>,
17394 ) -> Task<Result<Navigated>> {
17395 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17396 }
17397
17398 pub fn go_to_declaration_split(
17399 &mut self,
17400 _: &GoToDeclaration,
17401 window: &mut Window,
17402 cx: &mut Context<Self>,
17403 ) -> Task<Result<Navigated>> {
17404 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17405 }
17406
17407 pub fn go_to_implementation(
17408 &mut self,
17409 _: &GoToImplementation,
17410 window: &mut Window,
17411 cx: &mut Context<Self>,
17412 ) -> Task<Result<Navigated>> {
17413 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17414 }
17415
17416 pub fn go_to_implementation_split(
17417 &mut self,
17418 _: &GoToImplementationSplit,
17419 window: &mut Window,
17420 cx: &mut Context<Self>,
17421 ) -> Task<Result<Navigated>> {
17422 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17423 }
17424
17425 pub fn go_to_type_definition(
17426 &mut self,
17427 _: &GoToTypeDefinition,
17428 window: &mut Window,
17429 cx: &mut Context<Self>,
17430 ) -> Task<Result<Navigated>> {
17431 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17432 }
17433
17434 pub fn go_to_definition_split(
17435 &mut self,
17436 _: &GoToDefinitionSplit,
17437 window: &mut Window,
17438 cx: &mut Context<Self>,
17439 ) -> Task<Result<Navigated>> {
17440 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17441 }
17442
17443 pub fn go_to_type_definition_split(
17444 &mut self,
17445 _: &GoToTypeDefinitionSplit,
17446 window: &mut Window,
17447 cx: &mut Context<Self>,
17448 ) -> Task<Result<Navigated>> {
17449 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17450 }
17451
17452 fn go_to_definition_of_kind(
17453 &mut self,
17454 kind: GotoDefinitionKind,
17455 split: bool,
17456 window: &mut Window,
17457 cx: &mut Context<Self>,
17458 ) -> Task<Result<Navigated>> {
17459 let Some(provider) = self.semantics_provider.clone() else {
17460 return Task::ready(Ok(Navigated::No));
17461 };
17462 let head = self
17463 .selections
17464 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17465 .head();
17466 let buffer = self.buffer.read(cx);
17467 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17468 return Task::ready(Ok(Navigated::No));
17469 };
17470 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17471 return Task::ready(Ok(Navigated::No));
17472 };
17473
17474 cx.spawn_in(window, async move |editor, cx| {
17475 let Some(definitions) = definitions.await? else {
17476 return Ok(Navigated::No);
17477 };
17478 let navigated = editor
17479 .update_in(cx, |editor, window, cx| {
17480 editor.navigate_to_hover_links(
17481 Some(kind),
17482 definitions
17483 .into_iter()
17484 .filter(|location| {
17485 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17486 })
17487 .map(HoverLink::Text)
17488 .collect::<Vec<_>>(),
17489 split,
17490 window,
17491 cx,
17492 )
17493 })?
17494 .await?;
17495 anyhow::Ok(navigated)
17496 })
17497 }
17498
17499 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17500 let selection = self.selections.newest_anchor();
17501 let head = selection.head();
17502 let tail = selection.tail();
17503
17504 let Some((buffer, start_position)) =
17505 self.buffer.read(cx).text_anchor_for_position(head, cx)
17506 else {
17507 return;
17508 };
17509
17510 let end_position = if head != tail {
17511 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17512 return;
17513 };
17514 Some(pos)
17515 } else {
17516 None
17517 };
17518
17519 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17520 let url = if let Some(end_pos) = end_position {
17521 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17522 } else {
17523 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17524 };
17525
17526 if let Some(url) = url {
17527 cx.update(|window, cx| {
17528 if parse_zed_link(&url, cx).is_some() {
17529 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17530 } else {
17531 cx.open_url(&url);
17532 }
17533 })?;
17534 }
17535
17536 anyhow::Ok(())
17537 });
17538
17539 url_finder.detach();
17540 }
17541
17542 pub fn open_selected_filename(
17543 &mut self,
17544 _: &OpenSelectedFilename,
17545 window: &mut Window,
17546 cx: &mut Context<Self>,
17547 ) {
17548 let Some(workspace) = self.workspace() else {
17549 return;
17550 };
17551
17552 let position = self.selections.newest_anchor().head();
17553
17554 let Some((buffer, buffer_position)) =
17555 self.buffer.read(cx).text_anchor_for_position(position, cx)
17556 else {
17557 return;
17558 };
17559
17560 let project = self.project.clone();
17561
17562 cx.spawn_in(window, async move |_, cx| {
17563 let result = find_file(&buffer, project, buffer_position, cx).await;
17564
17565 if let Some((_, path)) = result {
17566 workspace
17567 .update_in(cx, |workspace, window, cx| {
17568 workspace.open_resolved_path(path, window, cx)
17569 })?
17570 .await?;
17571 }
17572 anyhow::Ok(())
17573 })
17574 .detach();
17575 }
17576
17577 pub(crate) fn navigate_to_hover_links(
17578 &mut self,
17579 kind: Option<GotoDefinitionKind>,
17580 definitions: Vec<HoverLink>,
17581 split: bool,
17582 window: &mut Window,
17583 cx: &mut Context<Editor>,
17584 ) -> Task<Result<Navigated>> {
17585 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17586 let mut first_url_or_file = None;
17587 let definitions: Vec<_> = definitions
17588 .into_iter()
17589 .filter_map(|def| match def {
17590 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17591 HoverLink::InlayHint(lsp_location, server_id) => {
17592 let computation =
17593 self.compute_target_location(lsp_location, server_id, window, cx);
17594 Some(cx.background_spawn(computation))
17595 }
17596 HoverLink::Url(url) => {
17597 first_url_or_file = Some(Either::Left(url));
17598 None
17599 }
17600 HoverLink::File(path) => {
17601 first_url_or_file = Some(Either::Right(path));
17602 None
17603 }
17604 })
17605 .collect();
17606
17607 let workspace = self.workspace();
17608
17609 cx.spawn_in(window, async move |editor, cx| {
17610 let locations: Vec<Location> = future::join_all(definitions)
17611 .await
17612 .into_iter()
17613 .filter_map(|location| location.transpose())
17614 .collect::<Result<_>>()
17615 .context("location tasks")?;
17616 let mut locations = cx.update(|_, cx| {
17617 locations
17618 .into_iter()
17619 .map(|location| {
17620 let buffer = location.buffer.read(cx);
17621 (location.buffer, location.range.to_point(buffer))
17622 })
17623 .into_group_map()
17624 })?;
17625 let mut num_locations = 0;
17626 for ranges in locations.values_mut() {
17627 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17628 ranges.dedup();
17629 num_locations += ranges.len();
17630 }
17631
17632 if num_locations > 1 {
17633 let tab_kind = match kind {
17634 Some(GotoDefinitionKind::Implementation) => "Implementations",
17635 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17636 Some(GotoDefinitionKind::Declaration) => "Declarations",
17637 Some(GotoDefinitionKind::Type) => "Types",
17638 };
17639 let title = editor
17640 .update_in(cx, |_, _, cx| {
17641 let target = locations
17642 .iter()
17643 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17644 .map(|(buffer, location)| {
17645 buffer
17646 .read(cx)
17647 .text_for_range(location.clone())
17648 .collect::<String>()
17649 })
17650 .filter(|text| !text.contains('\n'))
17651 .unique()
17652 .take(3)
17653 .join(", ");
17654 if target.is_empty() {
17655 tab_kind.to_owned()
17656 } else {
17657 format!("{tab_kind} for {target}")
17658 }
17659 })
17660 .context("buffer title")?;
17661
17662 let Some(workspace) = workspace else {
17663 return Ok(Navigated::No);
17664 };
17665
17666 let opened = workspace
17667 .update_in(cx, |workspace, window, cx| {
17668 let allow_preview = PreviewTabsSettings::get_global(cx)
17669 .enable_preview_multibuffer_from_code_navigation;
17670 Self::open_locations_in_multibuffer(
17671 workspace,
17672 locations,
17673 title,
17674 split,
17675 allow_preview,
17676 MultibufferSelectionMode::First,
17677 window,
17678 cx,
17679 )
17680 })
17681 .is_ok();
17682
17683 anyhow::Ok(Navigated::from_bool(opened))
17684 } else if num_locations == 0 {
17685 // If there is one url or file, open it directly
17686 match first_url_or_file {
17687 Some(Either::Left(url)) => {
17688 cx.update(|window, cx| {
17689 if parse_zed_link(&url, cx).is_some() {
17690 window
17691 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17692 } else {
17693 cx.open_url(&url);
17694 }
17695 })?;
17696 Ok(Navigated::Yes)
17697 }
17698 Some(Either::Right(path)) => {
17699 // TODO(andrew): respect preview tab settings
17700 // `enable_keep_preview_on_code_navigation` and
17701 // `enable_preview_file_from_code_navigation`
17702 let Some(workspace) = workspace else {
17703 return Ok(Navigated::No);
17704 };
17705 workspace
17706 .update_in(cx, |workspace, window, cx| {
17707 workspace.open_resolved_path(path, window, cx)
17708 })?
17709 .await?;
17710 Ok(Navigated::Yes)
17711 }
17712 None => Ok(Navigated::No),
17713 }
17714 } else {
17715 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17716 let target_range = target_ranges.first().unwrap().clone();
17717
17718 editor.update_in(cx, |editor, window, cx| {
17719 let range = target_range.to_point(target_buffer.read(cx));
17720 let range = editor.range_for_match(&range);
17721 let range = collapse_multiline_range(range);
17722
17723 if !split
17724 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17725 {
17726 editor.go_to_singleton_buffer_range(range, window, cx);
17727 } else {
17728 let Some(workspace) = workspace else {
17729 return Navigated::No;
17730 };
17731 let pane = workspace.read(cx).active_pane().clone();
17732 window.defer(cx, move |window, cx| {
17733 let target_editor: Entity<Self> =
17734 workspace.update(cx, |workspace, cx| {
17735 let pane = if split {
17736 workspace.adjacent_pane(window, cx)
17737 } else {
17738 workspace.active_pane().clone()
17739 };
17740
17741 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17742 let keep_old_preview = preview_tabs_settings
17743 .enable_keep_preview_on_code_navigation;
17744 let allow_new_preview = preview_tabs_settings
17745 .enable_preview_file_from_code_navigation;
17746
17747 workspace.open_project_item(
17748 pane,
17749 target_buffer.clone(),
17750 true,
17751 true,
17752 keep_old_preview,
17753 allow_new_preview,
17754 window,
17755 cx,
17756 )
17757 });
17758 target_editor.update(cx, |target_editor, cx| {
17759 // When selecting a definition in a different buffer, disable the nav history
17760 // to avoid creating a history entry at the previous cursor location.
17761 pane.update(cx, |pane, _| pane.disable_history());
17762 target_editor.go_to_singleton_buffer_range(range, window, cx);
17763 pane.update(cx, |pane, _| pane.enable_history());
17764 });
17765 });
17766 }
17767 Navigated::Yes
17768 })
17769 }
17770 })
17771 }
17772
17773 fn compute_target_location(
17774 &self,
17775 lsp_location: lsp::Location,
17776 server_id: LanguageServerId,
17777 window: &mut Window,
17778 cx: &mut Context<Self>,
17779 ) -> Task<anyhow::Result<Option<Location>>> {
17780 let Some(project) = self.project.clone() else {
17781 return Task::ready(Ok(None));
17782 };
17783
17784 cx.spawn_in(window, async move |editor, cx| {
17785 let location_task = editor.update(cx, |_, cx| {
17786 project.update(cx, |project, cx| {
17787 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17788 })
17789 })?;
17790 let location = Some({
17791 let target_buffer_handle = location_task.await.context("open local buffer")?;
17792 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17793 let target_start = target_buffer
17794 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17795 let target_end = target_buffer
17796 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17797 target_buffer.anchor_after(target_start)
17798 ..target_buffer.anchor_before(target_end)
17799 });
17800 Location {
17801 buffer: target_buffer_handle,
17802 range,
17803 }
17804 });
17805 Ok(location)
17806 })
17807 }
17808
17809 fn go_to_next_reference(
17810 &mut self,
17811 _: &GoToNextReference,
17812 window: &mut Window,
17813 cx: &mut Context<Self>,
17814 ) {
17815 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17816 if let Some(task) = task {
17817 task.detach();
17818 };
17819 }
17820
17821 fn go_to_prev_reference(
17822 &mut self,
17823 _: &GoToPreviousReference,
17824 window: &mut Window,
17825 cx: &mut Context<Self>,
17826 ) {
17827 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17828 if let Some(task) = task {
17829 task.detach();
17830 };
17831 }
17832
17833 pub fn go_to_reference_before_or_after_position(
17834 &mut self,
17835 direction: Direction,
17836 count: usize,
17837 window: &mut Window,
17838 cx: &mut Context<Self>,
17839 ) -> Option<Task<Result<()>>> {
17840 let selection = self.selections.newest_anchor();
17841 let head = selection.head();
17842
17843 let multi_buffer = self.buffer.read(cx);
17844
17845 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17846 let workspace = self.workspace()?;
17847 let project = workspace.read(cx).project().clone();
17848 let references =
17849 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17850 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17851 let Some(locations) = references.await? else {
17852 return Ok(());
17853 };
17854
17855 if locations.is_empty() {
17856 // totally normal - the cursor may be on something which is not
17857 // a symbol (e.g. a keyword)
17858 log::info!("no references found under cursor");
17859 return Ok(());
17860 }
17861
17862 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17863
17864 let (locations, current_location_index) =
17865 multi_buffer.update(cx, |multi_buffer, cx| {
17866 let mut locations = locations
17867 .into_iter()
17868 .filter_map(|loc| {
17869 let start = multi_buffer.buffer_anchor_to_anchor(
17870 &loc.buffer,
17871 loc.range.start,
17872 cx,
17873 )?;
17874 let end = multi_buffer.buffer_anchor_to_anchor(
17875 &loc.buffer,
17876 loc.range.end,
17877 cx,
17878 )?;
17879 Some(start..end)
17880 })
17881 .collect::<Vec<_>>();
17882
17883 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17884 // There is an O(n) implementation, but given this list will be
17885 // small (usually <100 items), the extra O(log(n)) factor isn't
17886 // worth the (surprisingly large amount of) extra complexity.
17887 locations
17888 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17889
17890 let head_offset = head.to_offset(&multi_buffer_snapshot);
17891
17892 let current_location_index = locations.iter().position(|loc| {
17893 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17894 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17895 });
17896
17897 (locations, current_location_index)
17898 });
17899
17900 let Some(current_location_index) = current_location_index else {
17901 // This indicates something has gone wrong, because we already
17902 // handle the "no references" case above
17903 log::error!(
17904 "failed to find current reference under cursor. Total references: {}",
17905 locations.len()
17906 );
17907 return Ok(());
17908 };
17909
17910 let destination_location_index = match direction {
17911 Direction::Next => (current_location_index + count) % locations.len(),
17912 Direction::Prev => {
17913 (current_location_index + locations.len() - count % locations.len())
17914 % locations.len()
17915 }
17916 };
17917
17918 // TODO(cameron): is this needed?
17919 // the thinking is to avoid "jumping to the current location" (avoid
17920 // polluting "jumplist" in vim terms)
17921 if current_location_index == destination_location_index {
17922 return Ok(());
17923 }
17924
17925 let Range { start, end } = locations[destination_location_index];
17926
17927 editor.update_in(cx, |editor, window, cx| {
17928 let effects = SelectionEffects::default();
17929
17930 editor.unfold_ranges(&[start..end], false, false, cx);
17931 editor.change_selections(effects, window, cx, |s| {
17932 s.select_ranges([start..start]);
17933 });
17934 })?;
17935
17936 Ok(())
17937 }))
17938 }
17939
17940 pub fn find_all_references(
17941 &mut self,
17942 action: &FindAllReferences,
17943 window: &mut Window,
17944 cx: &mut Context<Self>,
17945 ) -> Option<Task<Result<Navigated>>> {
17946 let always_open_multibuffer = action.always_open_multibuffer;
17947 let selection = self.selections.newest_anchor();
17948 let multi_buffer = self.buffer.read(cx);
17949 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17950 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17951 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17952 let head = selection_offset.head();
17953
17954 let head_anchor = multi_buffer_snapshot.anchor_at(
17955 head,
17956 if head < selection_offset.tail() {
17957 Bias::Right
17958 } else {
17959 Bias::Left
17960 },
17961 );
17962
17963 match self
17964 .find_all_references_task_sources
17965 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17966 {
17967 Ok(_) => {
17968 log::info!(
17969 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17970 );
17971 return None;
17972 }
17973 Err(i) => {
17974 self.find_all_references_task_sources.insert(i, head_anchor);
17975 }
17976 }
17977
17978 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17979 let workspace = self.workspace()?;
17980 let project = workspace.read(cx).project().clone();
17981 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17982 Some(cx.spawn_in(window, async move |editor, cx| {
17983 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17984 if let Ok(i) = editor
17985 .find_all_references_task_sources
17986 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17987 {
17988 editor.find_all_references_task_sources.remove(i);
17989 }
17990 });
17991
17992 let Some(locations) = references.await? else {
17993 return anyhow::Ok(Navigated::No);
17994 };
17995 let mut locations = cx.update(|_, cx| {
17996 locations
17997 .into_iter()
17998 .map(|location| {
17999 let buffer = location.buffer.read(cx);
18000 (location.buffer, location.range.to_point(buffer))
18001 })
18002 // if special-casing the single-match case, remove ranges
18003 // that intersect current selection
18004 .filter(|(location_buffer, location)| {
18005 if always_open_multibuffer || &buffer != location_buffer {
18006 return true;
18007 }
18008
18009 !location.contains_inclusive(&selection_point.range())
18010 })
18011 .into_group_map()
18012 })?;
18013 if locations.is_empty() {
18014 return anyhow::Ok(Navigated::No);
18015 }
18016 for ranges in locations.values_mut() {
18017 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18018 ranges.dedup();
18019 }
18020 let mut num_locations = 0;
18021 for ranges in locations.values_mut() {
18022 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18023 ranges.dedup();
18024 num_locations += ranges.len();
18025 }
18026
18027 if num_locations == 1 && !always_open_multibuffer {
18028 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18029 let target_range = target_ranges.first().unwrap().clone();
18030
18031 return editor.update_in(cx, |editor, window, cx| {
18032 let range = target_range.to_point(target_buffer.read(cx));
18033 let range = editor.range_for_match(&range);
18034 let range = range.start..range.start;
18035
18036 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18037 editor.go_to_singleton_buffer_range(range, window, cx);
18038 } else {
18039 let pane = workspace.read(cx).active_pane().clone();
18040 window.defer(cx, move |window, cx| {
18041 let target_editor: Entity<Self> =
18042 workspace.update(cx, |workspace, cx| {
18043 let pane = workspace.active_pane().clone();
18044
18045 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18046 let keep_old_preview = preview_tabs_settings
18047 .enable_keep_preview_on_code_navigation;
18048 let allow_new_preview = preview_tabs_settings
18049 .enable_preview_file_from_code_navigation;
18050
18051 workspace.open_project_item(
18052 pane,
18053 target_buffer.clone(),
18054 true,
18055 true,
18056 keep_old_preview,
18057 allow_new_preview,
18058 window,
18059 cx,
18060 )
18061 });
18062 target_editor.update(cx, |target_editor, cx| {
18063 // When selecting a definition in a different buffer, disable the nav history
18064 // to avoid creating a history entry at the previous cursor location.
18065 pane.update(cx, |pane, _| pane.disable_history());
18066 target_editor.go_to_singleton_buffer_range(range, window, cx);
18067 pane.update(cx, |pane, _| pane.enable_history());
18068 });
18069 });
18070 }
18071 Navigated::No
18072 });
18073 }
18074
18075 workspace.update_in(cx, |workspace, window, cx| {
18076 let target = locations
18077 .iter()
18078 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18079 .map(|(buffer, location)| {
18080 buffer
18081 .read(cx)
18082 .text_for_range(location.clone())
18083 .collect::<String>()
18084 })
18085 .filter(|text| !text.contains('\n'))
18086 .unique()
18087 .take(3)
18088 .join(", ");
18089 let title = if target.is_empty() {
18090 "References".to_owned()
18091 } else {
18092 format!("References to {target}")
18093 };
18094 let allow_preview = PreviewTabsSettings::get_global(cx)
18095 .enable_preview_multibuffer_from_code_navigation;
18096 Self::open_locations_in_multibuffer(
18097 workspace,
18098 locations,
18099 title,
18100 false,
18101 allow_preview,
18102 MultibufferSelectionMode::First,
18103 window,
18104 cx,
18105 );
18106 Navigated::Yes
18107 })
18108 }))
18109 }
18110
18111 /// Opens a multibuffer with the given project locations in it.
18112 pub fn open_locations_in_multibuffer(
18113 workspace: &mut Workspace,
18114 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18115 title: String,
18116 split: bool,
18117 allow_preview: bool,
18118 multibuffer_selection_mode: MultibufferSelectionMode,
18119 window: &mut Window,
18120 cx: &mut Context<Workspace>,
18121 ) {
18122 if locations.is_empty() {
18123 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18124 return;
18125 }
18126
18127 let capability = workspace.project().read(cx).capability();
18128 let mut ranges = <Vec<Range<Anchor>>>::new();
18129
18130 // a key to find existing multibuffer editors with the same set of locations
18131 // to prevent us from opening more and more multibuffer tabs for searches and the like
18132 let mut key = (title.clone(), vec![]);
18133 let excerpt_buffer = cx.new(|cx| {
18134 let key = &mut key.1;
18135 let mut multibuffer = MultiBuffer::new(capability);
18136 for (buffer, mut ranges_for_buffer) in locations {
18137 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18138 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18139 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18140 PathKey::for_buffer(&buffer, cx),
18141 buffer.clone(),
18142 ranges_for_buffer,
18143 multibuffer_context_lines(cx),
18144 cx,
18145 );
18146 ranges.extend(new_ranges)
18147 }
18148
18149 multibuffer.with_title(title)
18150 });
18151 let existing = workspace.active_pane().update(cx, |pane, cx| {
18152 pane.items()
18153 .filter_map(|item| item.downcast::<Editor>())
18154 .find(|editor| {
18155 editor
18156 .read(cx)
18157 .lookup_key
18158 .as_ref()
18159 .and_then(|it| {
18160 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18161 })
18162 .is_some_and(|it| *it == key)
18163 })
18164 });
18165 let was_existing = existing.is_some();
18166 let editor = existing.unwrap_or_else(|| {
18167 cx.new(|cx| {
18168 let mut editor = Editor::for_multibuffer(
18169 excerpt_buffer,
18170 Some(workspace.project().clone()),
18171 window,
18172 cx,
18173 );
18174 editor.lookup_key = Some(Box::new(key));
18175 editor
18176 })
18177 });
18178 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18179 MultibufferSelectionMode::First => {
18180 if let Some(first_range) = ranges.first() {
18181 editor.change_selections(
18182 SelectionEffects::no_scroll(),
18183 window,
18184 cx,
18185 |selections| {
18186 selections.clear_disjoint();
18187 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18188 },
18189 );
18190 }
18191 editor.highlight_background::<Self>(
18192 &ranges,
18193 |_, theme| theme.colors().editor_highlighted_line_background,
18194 cx,
18195 );
18196 }
18197 MultibufferSelectionMode::All => {
18198 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18199 selections.clear_disjoint();
18200 selections.select_anchor_ranges(ranges);
18201 });
18202 }
18203 });
18204
18205 let item = Box::new(editor);
18206
18207 let pane = if split {
18208 workspace.adjacent_pane(window, cx)
18209 } else {
18210 workspace.active_pane().clone()
18211 };
18212 let activate_pane = split;
18213
18214 let mut destination_index = None;
18215 pane.update(cx, |pane, cx| {
18216 if allow_preview && !was_existing {
18217 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18218 }
18219 if was_existing && !allow_preview {
18220 pane.unpreview_item_if_preview(item.item_id());
18221 }
18222 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18223 });
18224 }
18225
18226 pub fn rename(
18227 &mut self,
18228 _: &Rename,
18229 window: &mut Window,
18230 cx: &mut Context<Self>,
18231 ) -> Option<Task<Result<()>>> {
18232 use language::ToOffset as _;
18233
18234 let provider = self.semantics_provider.clone()?;
18235 let selection = self.selections.newest_anchor().clone();
18236 let (cursor_buffer, cursor_buffer_position) = self
18237 .buffer
18238 .read(cx)
18239 .text_anchor_for_position(selection.head(), cx)?;
18240 let (tail_buffer, cursor_buffer_position_end) = self
18241 .buffer
18242 .read(cx)
18243 .text_anchor_for_position(selection.tail(), cx)?;
18244 if tail_buffer != cursor_buffer {
18245 return None;
18246 }
18247
18248 let snapshot = cursor_buffer.read(cx).snapshot();
18249 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18250 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18251 let prepare_rename = provider
18252 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18253 .unwrap_or_else(|| Task::ready(Ok(None)));
18254 drop(snapshot);
18255
18256 Some(cx.spawn_in(window, async move |this, cx| {
18257 let rename_range = if let Some(range) = prepare_rename.await? {
18258 Some(range)
18259 } else {
18260 this.update(cx, |this, cx| {
18261 let buffer = this.buffer.read(cx).snapshot(cx);
18262 let mut buffer_highlights = this
18263 .document_highlights_for_position(selection.head(), &buffer)
18264 .filter(|highlight| {
18265 highlight.start.excerpt_id == selection.head().excerpt_id
18266 && highlight.end.excerpt_id == selection.head().excerpt_id
18267 });
18268 buffer_highlights
18269 .next()
18270 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18271 })?
18272 };
18273 if let Some(rename_range) = rename_range {
18274 this.update_in(cx, |this, window, cx| {
18275 let snapshot = cursor_buffer.read(cx).snapshot();
18276 let rename_buffer_range = rename_range.to_offset(&snapshot);
18277 let cursor_offset_in_rename_range =
18278 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18279 let cursor_offset_in_rename_range_end =
18280 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18281
18282 this.take_rename(false, window, cx);
18283 let buffer = this.buffer.read(cx).read(cx);
18284 let cursor_offset = selection.head().to_offset(&buffer);
18285 let rename_start =
18286 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18287 let rename_end = rename_start + rename_buffer_range.len();
18288 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18289 let mut old_highlight_id = None;
18290 let old_name: Arc<str> = buffer
18291 .chunks(rename_start..rename_end, true)
18292 .map(|chunk| {
18293 if old_highlight_id.is_none() {
18294 old_highlight_id = chunk.syntax_highlight_id;
18295 }
18296 chunk.text
18297 })
18298 .collect::<String>()
18299 .into();
18300
18301 drop(buffer);
18302
18303 // Position the selection in the rename editor so that it matches the current selection.
18304 this.show_local_selections = false;
18305 let rename_editor = cx.new(|cx| {
18306 let mut editor = Editor::single_line(window, cx);
18307 editor.buffer.update(cx, |buffer, cx| {
18308 buffer.edit(
18309 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18310 None,
18311 cx,
18312 )
18313 });
18314 let cursor_offset_in_rename_range =
18315 MultiBufferOffset(cursor_offset_in_rename_range);
18316 let cursor_offset_in_rename_range_end =
18317 MultiBufferOffset(cursor_offset_in_rename_range_end);
18318 let rename_selection_range = match cursor_offset_in_rename_range
18319 .cmp(&cursor_offset_in_rename_range_end)
18320 {
18321 Ordering::Equal => {
18322 editor.select_all(&SelectAll, window, cx);
18323 return editor;
18324 }
18325 Ordering::Less => {
18326 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18327 }
18328 Ordering::Greater => {
18329 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18330 }
18331 };
18332 if rename_selection_range.end.0 > old_name.len() {
18333 editor.select_all(&SelectAll, window, cx);
18334 } else {
18335 editor.change_selections(Default::default(), window, cx, |s| {
18336 s.select_ranges([rename_selection_range]);
18337 });
18338 }
18339 editor
18340 });
18341 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18342 if e == &EditorEvent::Focused {
18343 cx.emit(EditorEvent::FocusedIn)
18344 }
18345 })
18346 .detach();
18347
18348 let write_highlights =
18349 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18350 let read_highlights =
18351 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18352 let ranges = write_highlights
18353 .iter()
18354 .flat_map(|(_, ranges)| ranges.iter())
18355 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18356 .cloned()
18357 .collect();
18358
18359 this.highlight_text::<Rename>(
18360 ranges,
18361 HighlightStyle {
18362 fade_out: Some(0.6),
18363 ..Default::default()
18364 },
18365 cx,
18366 );
18367 let rename_focus_handle = rename_editor.focus_handle(cx);
18368 window.focus(&rename_focus_handle, cx);
18369 let block_id = this.insert_blocks(
18370 [BlockProperties {
18371 style: BlockStyle::Flex,
18372 placement: BlockPlacement::Below(range.start),
18373 height: Some(1),
18374 render: Arc::new({
18375 let rename_editor = rename_editor.clone();
18376 move |cx: &mut BlockContext| {
18377 let mut text_style = cx.editor_style.text.clone();
18378 if let Some(highlight_style) = old_highlight_id
18379 .and_then(|h| h.style(&cx.editor_style.syntax))
18380 {
18381 text_style = text_style.highlight(highlight_style);
18382 }
18383 div()
18384 .block_mouse_except_scroll()
18385 .pl(cx.anchor_x)
18386 .child(EditorElement::new(
18387 &rename_editor,
18388 EditorStyle {
18389 background: cx.theme().system().transparent,
18390 local_player: cx.editor_style.local_player,
18391 text: text_style,
18392 scrollbar_width: cx.editor_style.scrollbar_width,
18393 syntax: cx.editor_style.syntax.clone(),
18394 status: cx.editor_style.status.clone(),
18395 inlay_hints_style: HighlightStyle {
18396 font_weight: Some(FontWeight::BOLD),
18397 ..make_inlay_hints_style(cx.app)
18398 },
18399 edit_prediction_styles: make_suggestion_styles(
18400 cx.app,
18401 ),
18402 ..EditorStyle::default()
18403 },
18404 ))
18405 .into_any_element()
18406 }
18407 }),
18408 priority: 0,
18409 }],
18410 Some(Autoscroll::fit()),
18411 cx,
18412 )[0];
18413 this.pending_rename = Some(RenameState {
18414 range,
18415 old_name,
18416 editor: rename_editor,
18417 block_id,
18418 });
18419 })?;
18420 }
18421
18422 Ok(())
18423 }))
18424 }
18425
18426 pub fn confirm_rename(
18427 &mut self,
18428 _: &ConfirmRename,
18429 window: &mut Window,
18430 cx: &mut Context<Self>,
18431 ) -> Option<Task<Result<()>>> {
18432 let rename = self.take_rename(false, window, cx)?;
18433 let workspace = self.workspace()?.downgrade();
18434 let (buffer, start) = self
18435 .buffer
18436 .read(cx)
18437 .text_anchor_for_position(rename.range.start, cx)?;
18438 let (end_buffer, _) = self
18439 .buffer
18440 .read(cx)
18441 .text_anchor_for_position(rename.range.end, cx)?;
18442 if buffer != end_buffer {
18443 return None;
18444 }
18445
18446 let old_name = rename.old_name;
18447 let new_name = rename.editor.read(cx).text(cx);
18448
18449 let rename = self.semantics_provider.as_ref()?.perform_rename(
18450 &buffer,
18451 start,
18452 new_name.clone(),
18453 cx,
18454 )?;
18455
18456 Some(cx.spawn_in(window, async move |editor, cx| {
18457 let project_transaction = rename.await?;
18458 Self::open_project_transaction(
18459 &editor,
18460 workspace,
18461 project_transaction,
18462 format!("Rename: {} → {}", old_name, new_name),
18463 cx,
18464 )
18465 .await?;
18466
18467 editor.update(cx, |editor, cx| {
18468 editor.refresh_document_highlights(cx);
18469 })?;
18470 Ok(())
18471 }))
18472 }
18473
18474 fn take_rename(
18475 &mut self,
18476 moving_cursor: bool,
18477 window: &mut Window,
18478 cx: &mut Context<Self>,
18479 ) -> Option<RenameState> {
18480 let rename = self.pending_rename.take()?;
18481 if rename.editor.focus_handle(cx).is_focused(window) {
18482 window.focus(&self.focus_handle, cx);
18483 }
18484
18485 self.remove_blocks(
18486 [rename.block_id].into_iter().collect(),
18487 Some(Autoscroll::fit()),
18488 cx,
18489 );
18490 self.clear_highlights::<Rename>(cx);
18491 self.show_local_selections = true;
18492
18493 if moving_cursor {
18494 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18495 editor
18496 .selections
18497 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18498 .head()
18499 });
18500
18501 // Update the selection to match the position of the selection inside
18502 // the rename editor.
18503 let snapshot = self.buffer.read(cx).read(cx);
18504 let rename_range = rename.range.to_offset(&snapshot);
18505 let cursor_in_editor = snapshot
18506 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18507 .min(rename_range.end);
18508 drop(snapshot);
18509
18510 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18511 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18512 });
18513 } else {
18514 self.refresh_document_highlights(cx);
18515 }
18516
18517 Some(rename)
18518 }
18519
18520 pub fn pending_rename(&self) -> Option<&RenameState> {
18521 self.pending_rename.as_ref()
18522 }
18523
18524 fn format(
18525 &mut self,
18526 _: &Format,
18527 window: &mut Window,
18528 cx: &mut Context<Self>,
18529 ) -> Option<Task<Result<()>>> {
18530 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18531
18532 let project = match &self.project {
18533 Some(project) => project.clone(),
18534 None => return None,
18535 };
18536
18537 Some(self.perform_format(
18538 project,
18539 FormatTrigger::Manual,
18540 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18541 window,
18542 cx,
18543 ))
18544 }
18545
18546 fn format_selections(
18547 &mut self,
18548 _: &FormatSelections,
18549 window: &mut Window,
18550 cx: &mut Context<Self>,
18551 ) -> Option<Task<Result<()>>> {
18552 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18553
18554 let project = match &self.project {
18555 Some(project) => project.clone(),
18556 None => return None,
18557 };
18558
18559 let ranges = self
18560 .selections
18561 .all_adjusted(&self.display_snapshot(cx))
18562 .into_iter()
18563 .map(|selection| selection.range())
18564 .collect_vec();
18565
18566 Some(self.perform_format(
18567 project,
18568 FormatTrigger::Manual,
18569 FormatTarget::Ranges(ranges),
18570 window,
18571 cx,
18572 ))
18573 }
18574
18575 fn perform_format(
18576 &mut self,
18577 project: Entity<Project>,
18578 trigger: FormatTrigger,
18579 target: FormatTarget,
18580 window: &mut Window,
18581 cx: &mut Context<Self>,
18582 ) -> Task<Result<()>> {
18583 let buffer = self.buffer.clone();
18584 let (buffers, target) = match target {
18585 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18586 FormatTarget::Ranges(selection_ranges) => {
18587 let multi_buffer = buffer.read(cx);
18588 let snapshot = multi_buffer.read(cx);
18589 let mut buffers = HashSet::default();
18590 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18591 BTreeMap::new();
18592 for selection_range in selection_ranges {
18593 for (buffer, buffer_range, _) in
18594 snapshot.range_to_buffer_ranges(selection_range)
18595 {
18596 let buffer_id = buffer.remote_id();
18597 let start = buffer.anchor_before(buffer_range.start);
18598 let end = buffer.anchor_after(buffer_range.end);
18599 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18600 buffer_id_to_ranges
18601 .entry(buffer_id)
18602 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18603 .or_insert_with(|| vec![start..end]);
18604 }
18605 }
18606 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18607 }
18608 };
18609
18610 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18611 let selections_prev = transaction_id_prev
18612 .and_then(|transaction_id_prev| {
18613 // default to selections as they were after the last edit, if we have them,
18614 // instead of how they are now.
18615 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18616 // will take you back to where you made the last edit, instead of staying where you scrolled
18617 self.selection_history
18618 .transaction(transaction_id_prev)
18619 .map(|t| t.0.clone())
18620 })
18621 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18622
18623 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18624 let format = project.update(cx, |project, cx| {
18625 project.format(buffers, target, true, trigger, cx)
18626 });
18627
18628 cx.spawn_in(window, async move |editor, cx| {
18629 let transaction = futures::select_biased! {
18630 transaction = format.log_err().fuse() => transaction,
18631 () = timeout => {
18632 log::warn!("timed out waiting for formatting");
18633 None
18634 }
18635 };
18636
18637 buffer.update(cx, |buffer, cx| {
18638 if let Some(transaction) = transaction
18639 && !buffer.is_singleton()
18640 {
18641 buffer.push_transaction(&transaction.0, cx);
18642 }
18643 cx.notify();
18644 });
18645
18646 if let Some(transaction_id_now) =
18647 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18648 {
18649 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18650 if has_new_transaction {
18651 editor
18652 .update(cx, |editor, _| {
18653 editor
18654 .selection_history
18655 .insert_transaction(transaction_id_now, selections_prev);
18656 })
18657 .ok();
18658 }
18659 }
18660
18661 Ok(())
18662 })
18663 }
18664
18665 fn organize_imports(
18666 &mut self,
18667 _: &OrganizeImports,
18668 window: &mut Window,
18669 cx: &mut Context<Self>,
18670 ) -> Option<Task<Result<()>>> {
18671 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18672 let project = match &self.project {
18673 Some(project) => project.clone(),
18674 None => return None,
18675 };
18676 Some(self.perform_code_action_kind(
18677 project,
18678 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18679 window,
18680 cx,
18681 ))
18682 }
18683
18684 fn perform_code_action_kind(
18685 &mut self,
18686 project: Entity<Project>,
18687 kind: CodeActionKind,
18688 window: &mut Window,
18689 cx: &mut Context<Self>,
18690 ) -> Task<Result<()>> {
18691 let buffer = self.buffer.clone();
18692 let buffers = buffer.read(cx).all_buffers();
18693 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18694 let apply_action = project.update(cx, |project, cx| {
18695 project.apply_code_action_kind(buffers, kind, true, cx)
18696 });
18697 cx.spawn_in(window, async move |_, cx| {
18698 let transaction = futures::select_biased! {
18699 () = timeout => {
18700 log::warn!("timed out waiting for executing code action");
18701 None
18702 }
18703 transaction = apply_action.log_err().fuse() => transaction,
18704 };
18705 buffer.update(cx, |buffer, cx| {
18706 // check if we need this
18707 if let Some(transaction) = transaction
18708 && !buffer.is_singleton()
18709 {
18710 buffer.push_transaction(&transaction.0, cx);
18711 }
18712 cx.notify();
18713 });
18714 Ok(())
18715 })
18716 }
18717
18718 pub fn restart_language_server(
18719 &mut self,
18720 _: &RestartLanguageServer,
18721 _: &mut Window,
18722 cx: &mut Context<Self>,
18723 ) {
18724 if let Some(project) = self.project.clone() {
18725 self.buffer.update(cx, |multi_buffer, cx| {
18726 project.update(cx, |project, cx| {
18727 project.restart_language_servers_for_buffers(
18728 multi_buffer.all_buffers().into_iter().collect(),
18729 HashSet::default(),
18730 cx,
18731 );
18732 });
18733 })
18734 }
18735 }
18736
18737 pub fn stop_language_server(
18738 &mut self,
18739 _: &StopLanguageServer,
18740 _: &mut Window,
18741 cx: &mut Context<Self>,
18742 ) {
18743 if let Some(project) = self.project.clone() {
18744 self.buffer.update(cx, |multi_buffer, cx| {
18745 project.update(cx, |project, cx| {
18746 project.stop_language_servers_for_buffers(
18747 multi_buffer.all_buffers().into_iter().collect(),
18748 HashSet::default(),
18749 cx,
18750 );
18751 });
18752 });
18753 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18754 }
18755 }
18756
18757 fn cancel_language_server_work(
18758 workspace: &mut Workspace,
18759 _: &actions::CancelLanguageServerWork,
18760 _: &mut Window,
18761 cx: &mut Context<Workspace>,
18762 ) {
18763 let project = workspace.project();
18764 let buffers = workspace
18765 .active_item(cx)
18766 .and_then(|item| item.act_as::<Editor>(cx))
18767 .map_or(HashSet::default(), |editor| {
18768 editor.read(cx).buffer.read(cx).all_buffers()
18769 });
18770 project.update(cx, |project, cx| {
18771 project.cancel_language_server_work_for_buffers(buffers, cx);
18772 });
18773 }
18774
18775 fn show_character_palette(
18776 &mut self,
18777 _: &ShowCharacterPalette,
18778 window: &mut Window,
18779 _: &mut Context<Self>,
18780 ) {
18781 window.show_character_palette();
18782 }
18783
18784 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18785 if !self.diagnostics_enabled() {
18786 return;
18787 }
18788
18789 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18790 let buffer = self.buffer.read(cx).snapshot(cx);
18791 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18792 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18793 let is_valid = buffer
18794 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18795 .any(|entry| {
18796 entry.diagnostic.is_primary
18797 && !entry.range.is_empty()
18798 && entry.range.start == primary_range_start
18799 && entry.diagnostic.message == active_diagnostics.active_message
18800 });
18801
18802 if !is_valid {
18803 self.dismiss_diagnostics(cx);
18804 }
18805 }
18806 }
18807
18808 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18809 match &self.active_diagnostics {
18810 ActiveDiagnostic::Group(group) => Some(group),
18811 _ => None,
18812 }
18813 }
18814
18815 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18816 if !self.diagnostics_enabled() {
18817 return;
18818 }
18819 self.dismiss_diagnostics(cx);
18820 self.active_diagnostics = ActiveDiagnostic::All;
18821 }
18822
18823 fn activate_diagnostics(
18824 &mut self,
18825 buffer_id: BufferId,
18826 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18827 window: &mut Window,
18828 cx: &mut Context<Self>,
18829 ) {
18830 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18831 return;
18832 }
18833 self.dismiss_diagnostics(cx);
18834 let snapshot = self.snapshot(window, cx);
18835 let buffer = self.buffer.read(cx).snapshot(cx);
18836 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18837 return;
18838 };
18839
18840 let diagnostic_group = buffer
18841 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18842 .collect::<Vec<_>>();
18843
18844 let language_registry = self
18845 .project()
18846 .map(|project| project.read(cx).languages().clone());
18847
18848 let blocks = renderer.render_group(
18849 diagnostic_group,
18850 buffer_id,
18851 snapshot,
18852 cx.weak_entity(),
18853 language_registry,
18854 cx,
18855 );
18856
18857 let blocks = self.display_map.update(cx, |display_map, cx| {
18858 display_map.insert_blocks(blocks, cx).into_iter().collect()
18859 });
18860 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18861 active_range: buffer.anchor_before(diagnostic.range.start)
18862 ..buffer.anchor_after(diagnostic.range.end),
18863 active_message: diagnostic.diagnostic.message.clone(),
18864 group_id: diagnostic.diagnostic.group_id,
18865 blocks,
18866 });
18867 cx.notify();
18868 }
18869
18870 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18871 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18872 return;
18873 };
18874
18875 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18876 if let ActiveDiagnostic::Group(group) = prev {
18877 self.display_map.update(cx, |display_map, cx| {
18878 display_map.remove_blocks(group.blocks, cx);
18879 });
18880 cx.notify();
18881 }
18882 }
18883
18884 /// Disable inline diagnostics rendering for this editor.
18885 pub fn disable_inline_diagnostics(&mut self) {
18886 self.inline_diagnostics_enabled = false;
18887 self.inline_diagnostics_update = Task::ready(());
18888 self.inline_diagnostics.clear();
18889 }
18890
18891 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18892 self.diagnostics_enabled = false;
18893 self.dismiss_diagnostics(cx);
18894 self.inline_diagnostics_update = Task::ready(());
18895 self.inline_diagnostics.clear();
18896 }
18897
18898 pub fn disable_word_completions(&mut self) {
18899 self.word_completions_enabled = false;
18900 }
18901
18902 pub fn diagnostics_enabled(&self) -> bool {
18903 self.diagnostics_enabled && self.mode.is_full()
18904 }
18905
18906 pub fn inline_diagnostics_enabled(&self) -> bool {
18907 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18908 }
18909
18910 pub fn show_inline_diagnostics(&self) -> bool {
18911 self.show_inline_diagnostics
18912 }
18913
18914 pub fn toggle_inline_diagnostics(
18915 &mut self,
18916 _: &ToggleInlineDiagnostics,
18917 window: &mut Window,
18918 cx: &mut Context<Editor>,
18919 ) {
18920 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18921 self.refresh_inline_diagnostics(false, window, cx);
18922 }
18923
18924 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18925 self.diagnostics_max_severity = severity;
18926 self.display_map.update(cx, |display_map, _| {
18927 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18928 });
18929 }
18930
18931 pub fn toggle_diagnostics(
18932 &mut self,
18933 _: &ToggleDiagnostics,
18934 window: &mut Window,
18935 cx: &mut Context<Editor>,
18936 ) {
18937 if !self.diagnostics_enabled() {
18938 return;
18939 }
18940
18941 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18942 EditorSettings::get_global(cx)
18943 .diagnostics_max_severity
18944 .filter(|severity| severity != &DiagnosticSeverity::Off)
18945 .unwrap_or(DiagnosticSeverity::Hint)
18946 } else {
18947 DiagnosticSeverity::Off
18948 };
18949 self.set_max_diagnostics_severity(new_severity, cx);
18950 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18951 self.active_diagnostics = ActiveDiagnostic::None;
18952 self.inline_diagnostics_update = Task::ready(());
18953 self.inline_diagnostics.clear();
18954 } else {
18955 self.refresh_inline_diagnostics(false, window, cx);
18956 }
18957
18958 cx.notify();
18959 }
18960
18961 pub fn toggle_minimap(
18962 &mut self,
18963 _: &ToggleMinimap,
18964 window: &mut Window,
18965 cx: &mut Context<Editor>,
18966 ) {
18967 if self.supports_minimap(cx) {
18968 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18969 }
18970 }
18971
18972 fn refresh_inline_diagnostics(
18973 &mut self,
18974 debounce: bool,
18975 window: &mut Window,
18976 cx: &mut Context<Self>,
18977 ) {
18978 let max_severity = ProjectSettings::get_global(cx)
18979 .diagnostics
18980 .inline
18981 .max_severity
18982 .unwrap_or(self.diagnostics_max_severity);
18983
18984 if !self.inline_diagnostics_enabled()
18985 || !self.diagnostics_enabled()
18986 || !self.show_inline_diagnostics
18987 || max_severity == DiagnosticSeverity::Off
18988 {
18989 self.inline_diagnostics_update = Task::ready(());
18990 self.inline_diagnostics.clear();
18991 return;
18992 }
18993
18994 let debounce_ms = ProjectSettings::get_global(cx)
18995 .diagnostics
18996 .inline
18997 .update_debounce_ms;
18998 let debounce = if debounce && debounce_ms > 0 {
18999 Some(Duration::from_millis(debounce_ms))
19000 } else {
19001 None
19002 };
19003 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19004 if let Some(debounce) = debounce {
19005 cx.background_executor().timer(debounce).await;
19006 }
19007 let Some(snapshot) = editor.upgrade().map(|editor| {
19008 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19009 }) else {
19010 return;
19011 };
19012
19013 let new_inline_diagnostics = cx
19014 .background_spawn(async move {
19015 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19016 for diagnostic_entry in
19017 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19018 {
19019 let message = diagnostic_entry
19020 .diagnostic
19021 .message
19022 .split_once('\n')
19023 .map(|(line, _)| line)
19024 .map(SharedString::new)
19025 .unwrap_or_else(|| {
19026 SharedString::new(&*diagnostic_entry.diagnostic.message)
19027 });
19028 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19029 let (Ok(i) | Err(i)) = inline_diagnostics
19030 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19031 inline_diagnostics.insert(
19032 i,
19033 (
19034 start_anchor,
19035 InlineDiagnostic {
19036 message,
19037 group_id: diagnostic_entry.diagnostic.group_id,
19038 start: diagnostic_entry.range.start.to_point(&snapshot),
19039 is_primary: diagnostic_entry.diagnostic.is_primary,
19040 severity: diagnostic_entry.diagnostic.severity,
19041 },
19042 ),
19043 );
19044 }
19045 inline_diagnostics
19046 })
19047 .await;
19048
19049 editor
19050 .update(cx, |editor, cx| {
19051 editor.inline_diagnostics = new_inline_diagnostics;
19052 cx.notify();
19053 })
19054 .ok();
19055 });
19056 }
19057
19058 fn pull_diagnostics(
19059 &mut self,
19060 buffer_id: Option<BufferId>,
19061 window: &Window,
19062 cx: &mut Context<Self>,
19063 ) -> Option<()> {
19064 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
19065 return None;
19066 }
19067 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19068 .diagnostics
19069 .lsp_pull_diagnostics;
19070 if !pull_diagnostics_settings.enabled {
19071 return None;
19072 }
19073 let project = self.project()?.downgrade();
19074
19075 let mut edited_buffer_ids = HashSet::default();
19076 let mut edited_worktree_ids = HashSet::default();
19077 let edited_buffers = match buffer_id {
19078 Some(buffer_id) => {
19079 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19080 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
19081 edited_buffer_ids.insert(buffer.read(cx).remote_id());
19082 edited_worktree_ids.insert(worktree_id);
19083 vec![buffer]
19084 }
19085 None => self
19086 .buffer()
19087 .read(cx)
19088 .all_buffers()
19089 .into_iter()
19090 .filter(|buffer| {
19091 let buffer = buffer.read(cx);
19092 match buffer.file().map(|f| f.worktree_id(cx)) {
19093 Some(worktree_id) => {
19094 edited_buffer_ids.insert(buffer.remote_id());
19095 edited_worktree_ids.insert(worktree_id);
19096 true
19097 }
19098 None => false,
19099 }
19100 })
19101 .collect::<Vec<_>>(),
19102 };
19103
19104 if edited_buffers.is_empty() {
19105 self.pull_diagnostics_task = Task::ready(());
19106 self.pull_diagnostics_background_task = Task::ready(());
19107 return None;
19108 }
19109
19110 let mut already_used_buffers = HashSet::default();
19111 let related_open_buffers = self
19112 .workspace
19113 .as_ref()
19114 .and_then(|(workspace, _)| workspace.upgrade())
19115 .into_iter()
19116 .flat_map(|workspace| workspace.read(cx).panes())
19117 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
19118 .filter(|editor| editor != &cx.entity())
19119 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
19120 .filter(|buffer| {
19121 let buffer = buffer.read(cx);
19122 let buffer_id = buffer.remote_id();
19123 if already_used_buffers.insert(buffer_id) {
19124 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
19125 return !edited_buffer_ids.contains(&buffer_id)
19126 && edited_worktree_ids.contains(&worktree_id);
19127 }
19128 }
19129 false
19130 })
19131 .collect::<Vec<_>>();
19132
19133 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19134 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
19135 if buffers.is_empty() {
19136 return Task::ready(());
19137 }
19138 let project_weak = project.clone();
19139 cx.spawn_in(window, async move |_, cx| {
19140 cx.background_executor().timer(delay).await;
19141
19142 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
19143 buffers
19144 .into_iter()
19145 .filter_map(|buffer| {
19146 project_weak
19147 .update(cx, |project, cx| {
19148 project.lsp_store().update(cx, |lsp_store, cx| {
19149 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19150 })
19151 })
19152 .ok()
19153 })
19154 .collect::<FuturesUnordered<_>>()
19155 }) else {
19156 return;
19157 };
19158
19159 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
19160 if let Err(e) = pull_task {
19161 log::error!("Failed to update project diagnostics: {e:#}");
19162 }
19163 }
19164 })
19165 };
19166
19167 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
19168 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
19169
19170 Some(())
19171 }
19172
19173 pub fn set_selections_from_remote(
19174 &mut self,
19175 selections: Vec<Selection<Anchor>>,
19176 pending_selection: Option<Selection<Anchor>>,
19177 window: &mut Window,
19178 cx: &mut Context<Self>,
19179 ) {
19180 let old_cursor_position = self.selections.newest_anchor().head();
19181 self.selections
19182 .change_with(&self.display_snapshot(cx), |s| {
19183 s.select_anchors(selections);
19184 if let Some(pending_selection) = pending_selection {
19185 s.set_pending(pending_selection, SelectMode::Character);
19186 } else {
19187 s.clear_pending();
19188 }
19189 });
19190 self.selections_did_change(
19191 false,
19192 &old_cursor_position,
19193 SelectionEffects::default(),
19194 window,
19195 cx,
19196 );
19197 }
19198
19199 pub fn transact(
19200 &mut self,
19201 window: &mut Window,
19202 cx: &mut Context<Self>,
19203 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19204 ) -> Option<TransactionId> {
19205 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19206 this.start_transaction_at(Instant::now(), window, cx);
19207 update(this, window, cx);
19208 this.end_transaction_at(Instant::now(), cx)
19209 })
19210 }
19211
19212 pub fn start_transaction_at(
19213 &mut self,
19214 now: Instant,
19215 window: &mut Window,
19216 cx: &mut Context<Self>,
19217 ) -> Option<TransactionId> {
19218 self.end_selection(window, cx);
19219 if let Some(tx_id) = self
19220 .buffer
19221 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19222 {
19223 self.selection_history
19224 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19225 cx.emit(EditorEvent::TransactionBegun {
19226 transaction_id: tx_id,
19227 });
19228 Some(tx_id)
19229 } else {
19230 None
19231 }
19232 }
19233
19234 pub fn end_transaction_at(
19235 &mut self,
19236 now: Instant,
19237 cx: &mut Context<Self>,
19238 ) -> Option<TransactionId> {
19239 if let Some(transaction_id) = self
19240 .buffer
19241 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19242 {
19243 if let Some((_, end_selections)) =
19244 self.selection_history.transaction_mut(transaction_id)
19245 {
19246 *end_selections = Some(self.selections.disjoint_anchors_arc());
19247 } else {
19248 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19249 }
19250
19251 cx.emit(EditorEvent::Edited { transaction_id });
19252 Some(transaction_id)
19253 } else {
19254 None
19255 }
19256 }
19257
19258 pub fn modify_transaction_selection_history(
19259 &mut self,
19260 transaction_id: TransactionId,
19261 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19262 ) -> bool {
19263 self.selection_history
19264 .transaction_mut(transaction_id)
19265 .map(modify)
19266 .is_some()
19267 }
19268
19269 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19270 if self.selection_mark_mode {
19271 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19272 s.move_with(|_, sel| {
19273 sel.collapse_to(sel.head(), SelectionGoal::None);
19274 });
19275 })
19276 }
19277 self.selection_mark_mode = true;
19278 cx.notify();
19279 }
19280
19281 pub fn swap_selection_ends(
19282 &mut self,
19283 _: &actions::SwapSelectionEnds,
19284 window: &mut Window,
19285 cx: &mut Context<Self>,
19286 ) {
19287 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19288 s.move_with(|_, sel| {
19289 if sel.start != sel.end {
19290 sel.reversed = !sel.reversed
19291 }
19292 });
19293 });
19294 self.request_autoscroll(Autoscroll::newest(), cx);
19295 cx.notify();
19296 }
19297
19298 pub fn toggle_focus(
19299 workspace: &mut Workspace,
19300 _: &actions::ToggleFocus,
19301 window: &mut Window,
19302 cx: &mut Context<Workspace>,
19303 ) {
19304 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19305 return;
19306 };
19307 workspace.activate_item(&item, true, true, window, cx);
19308 }
19309
19310 pub fn toggle_fold(
19311 &mut self,
19312 _: &actions::ToggleFold,
19313 window: &mut Window,
19314 cx: &mut Context<Self>,
19315 ) {
19316 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19317 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19318 let selection = self.selections.newest::<Point>(&display_map);
19319
19320 let range = if selection.is_empty() {
19321 let point = selection.head().to_display_point(&display_map);
19322 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19323 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19324 .to_point(&display_map);
19325 start..end
19326 } else {
19327 selection.range()
19328 };
19329 if display_map.folds_in_range(range).next().is_some() {
19330 self.unfold_lines(&Default::default(), window, cx)
19331 } else {
19332 self.fold(&Default::default(), window, cx)
19333 }
19334 } else {
19335 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19336 let buffer_ids: HashSet<_> = self
19337 .selections
19338 .disjoint_anchor_ranges()
19339 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19340 .collect();
19341
19342 let should_unfold = buffer_ids
19343 .iter()
19344 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19345
19346 for buffer_id in buffer_ids {
19347 if should_unfold {
19348 self.unfold_buffer(buffer_id, cx);
19349 } else {
19350 self.fold_buffer(buffer_id, cx);
19351 }
19352 }
19353 }
19354 }
19355
19356 pub fn toggle_fold_recursive(
19357 &mut self,
19358 _: &actions::ToggleFoldRecursive,
19359 window: &mut Window,
19360 cx: &mut Context<Self>,
19361 ) {
19362 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19363
19364 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19365 let range = if selection.is_empty() {
19366 let point = selection.head().to_display_point(&display_map);
19367 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19368 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19369 .to_point(&display_map);
19370 start..end
19371 } else {
19372 selection.range()
19373 };
19374 if display_map.folds_in_range(range).next().is_some() {
19375 self.unfold_recursive(&Default::default(), window, cx)
19376 } else {
19377 self.fold_recursive(&Default::default(), window, cx)
19378 }
19379 }
19380
19381 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19382 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19383 let mut to_fold = Vec::new();
19384 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19385 let selections = self.selections.all_adjusted(&display_map);
19386
19387 for selection in selections {
19388 let range = selection.range().sorted();
19389 let buffer_start_row = range.start.row;
19390
19391 if range.start.row != range.end.row {
19392 let mut found = false;
19393 let mut row = range.start.row;
19394 while row <= range.end.row {
19395 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19396 {
19397 found = true;
19398 row = crease.range().end.row + 1;
19399 to_fold.push(crease);
19400 } else {
19401 row += 1
19402 }
19403 }
19404 if found {
19405 continue;
19406 }
19407 }
19408
19409 for row in (0..=range.start.row).rev() {
19410 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19411 && crease.range().end.row >= buffer_start_row
19412 {
19413 to_fold.push(crease);
19414 if row <= range.start.row {
19415 break;
19416 }
19417 }
19418 }
19419 }
19420
19421 self.fold_creases(to_fold, true, window, cx);
19422 } else {
19423 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19424 let buffer_ids = self
19425 .selections
19426 .disjoint_anchor_ranges()
19427 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19428 .collect::<HashSet<_>>();
19429 for buffer_id in buffer_ids {
19430 self.fold_buffer(buffer_id, cx);
19431 }
19432 }
19433 }
19434
19435 pub fn toggle_fold_all(
19436 &mut self,
19437 _: &actions::ToggleFoldAll,
19438 window: &mut Window,
19439 cx: &mut Context<Self>,
19440 ) {
19441 let has_folds = if self.buffer.read(cx).is_singleton() {
19442 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19443 let has_folds = display_map
19444 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19445 .next()
19446 .is_some();
19447 has_folds
19448 } else {
19449 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19450 let has_folds = buffer_ids
19451 .iter()
19452 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19453 has_folds
19454 };
19455
19456 if has_folds {
19457 self.unfold_all(&actions::UnfoldAll, window, cx);
19458 } else {
19459 self.fold_all(&actions::FoldAll, window, cx);
19460 }
19461 }
19462
19463 fn fold_at_level(
19464 &mut self,
19465 fold_at: &FoldAtLevel,
19466 window: &mut Window,
19467 cx: &mut Context<Self>,
19468 ) {
19469 if !self.buffer.read(cx).is_singleton() {
19470 return;
19471 }
19472
19473 let fold_at_level = fold_at.0;
19474 let snapshot = self.buffer.read(cx).snapshot(cx);
19475 let mut to_fold = Vec::new();
19476 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19477
19478 let row_ranges_to_keep: Vec<Range<u32>> = self
19479 .selections
19480 .all::<Point>(&self.display_snapshot(cx))
19481 .into_iter()
19482 .map(|sel| sel.start.row..sel.end.row)
19483 .collect();
19484
19485 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19486 while start_row < end_row {
19487 match self
19488 .snapshot(window, cx)
19489 .crease_for_buffer_row(MultiBufferRow(start_row))
19490 {
19491 Some(crease) => {
19492 let nested_start_row = crease.range().start.row + 1;
19493 let nested_end_row = crease.range().end.row;
19494
19495 if current_level < fold_at_level {
19496 stack.push((nested_start_row, nested_end_row, current_level + 1));
19497 } else if current_level == fold_at_level {
19498 // Fold iff there is no selection completely contained within the fold region
19499 if !row_ranges_to_keep.iter().any(|selection| {
19500 selection.end >= nested_start_row
19501 && selection.start <= nested_end_row
19502 }) {
19503 to_fold.push(crease);
19504 }
19505 }
19506
19507 start_row = nested_end_row + 1;
19508 }
19509 None => start_row += 1,
19510 }
19511 }
19512 }
19513
19514 self.fold_creases(to_fold, true, window, cx);
19515 }
19516
19517 pub fn fold_at_level_1(
19518 &mut self,
19519 _: &actions::FoldAtLevel1,
19520 window: &mut Window,
19521 cx: &mut Context<Self>,
19522 ) {
19523 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19524 }
19525
19526 pub fn fold_at_level_2(
19527 &mut self,
19528 _: &actions::FoldAtLevel2,
19529 window: &mut Window,
19530 cx: &mut Context<Self>,
19531 ) {
19532 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19533 }
19534
19535 pub fn fold_at_level_3(
19536 &mut self,
19537 _: &actions::FoldAtLevel3,
19538 window: &mut Window,
19539 cx: &mut Context<Self>,
19540 ) {
19541 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19542 }
19543
19544 pub fn fold_at_level_4(
19545 &mut self,
19546 _: &actions::FoldAtLevel4,
19547 window: &mut Window,
19548 cx: &mut Context<Self>,
19549 ) {
19550 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19551 }
19552
19553 pub fn fold_at_level_5(
19554 &mut self,
19555 _: &actions::FoldAtLevel5,
19556 window: &mut Window,
19557 cx: &mut Context<Self>,
19558 ) {
19559 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19560 }
19561
19562 pub fn fold_at_level_6(
19563 &mut self,
19564 _: &actions::FoldAtLevel6,
19565 window: &mut Window,
19566 cx: &mut Context<Self>,
19567 ) {
19568 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19569 }
19570
19571 pub fn fold_at_level_7(
19572 &mut self,
19573 _: &actions::FoldAtLevel7,
19574 window: &mut Window,
19575 cx: &mut Context<Self>,
19576 ) {
19577 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19578 }
19579
19580 pub fn fold_at_level_8(
19581 &mut self,
19582 _: &actions::FoldAtLevel8,
19583 window: &mut Window,
19584 cx: &mut Context<Self>,
19585 ) {
19586 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19587 }
19588
19589 pub fn fold_at_level_9(
19590 &mut self,
19591 _: &actions::FoldAtLevel9,
19592 window: &mut Window,
19593 cx: &mut Context<Self>,
19594 ) {
19595 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19596 }
19597
19598 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19599 if self.buffer.read(cx).is_singleton() {
19600 let mut fold_ranges = Vec::new();
19601 let snapshot = self.buffer.read(cx).snapshot(cx);
19602
19603 for row in 0..snapshot.max_row().0 {
19604 if let Some(foldable_range) = self
19605 .snapshot(window, cx)
19606 .crease_for_buffer_row(MultiBufferRow(row))
19607 {
19608 fold_ranges.push(foldable_range);
19609 }
19610 }
19611
19612 self.fold_creases(fold_ranges, true, window, cx);
19613 } else {
19614 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19615 editor
19616 .update_in(cx, |editor, _, cx| {
19617 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19618 editor.fold_buffer(buffer_id, cx);
19619 }
19620 })
19621 .ok();
19622 });
19623 }
19624 cx.emit(SearchEvent::ResultsCollapsedChanged(
19625 CollapseDirection::Collapsed,
19626 ));
19627 }
19628
19629 pub fn fold_function_bodies(
19630 &mut self,
19631 _: &actions::FoldFunctionBodies,
19632 window: &mut Window,
19633 cx: &mut Context<Self>,
19634 ) {
19635 let snapshot = self.buffer.read(cx).snapshot(cx);
19636
19637 let ranges = snapshot
19638 .text_object_ranges(
19639 MultiBufferOffset(0)..snapshot.len(),
19640 TreeSitterOptions::default(),
19641 )
19642 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19643 .collect::<Vec<_>>();
19644
19645 let creases = ranges
19646 .into_iter()
19647 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19648 .collect();
19649
19650 self.fold_creases(creases, true, window, cx);
19651 }
19652
19653 pub fn fold_recursive(
19654 &mut self,
19655 _: &actions::FoldRecursive,
19656 window: &mut Window,
19657 cx: &mut Context<Self>,
19658 ) {
19659 let mut to_fold = Vec::new();
19660 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19661 let selections = self.selections.all_adjusted(&display_map);
19662
19663 for selection in selections {
19664 let range = selection.range().sorted();
19665 let buffer_start_row = range.start.row;
19666
19667 if range.start.row != range.end.row {
19668 let mut found = false;
19669 for row in range.start.row..=range.end.row {
19670 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19671 found = true;
19672 to_fold.push(crease);
19673 }
19674 }
19675 if found {
19676 continue;
19677 }
19678 }
19679
19680 for row in (0..=range.start.row).rev() {
19681 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19682 if crease.range().end.row >= buffer_start_row {
19683 to_fold.push(crease);
19684 } else {
19685 break;
19686 }
19687 }
19688 }
19689 }
19690
19691 self.fold_creases(to_fold, true, window, cx);
19692 }
19693
19694 pub fn fold_at(
19695 &mut self,
19696 buffer_row: MultiBufferRow,
19697 window: &mut Window,
19698 cx: &mut Context<Self>,
19699 ) {
19700 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19701
19702 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19703 let autoscroll = self
19704 .selections
19705 .all::<Point>(&display_map)
19706 .iter()
19707 .any(|selection| crease.range().overlaps(&selection.range()));
19708
19709 self.fold_creases(vec![crease], autoscroll, window, cx);
19710 }
19711 }
19712
19713 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19714 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19715 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19716 let buffer = display_map.buffer_snapshot();
19717 let selections = self.selections.all::<Point>(&display_map);
19718 let ranges = selections
19719 .iter()
19720 .map(|s| {
19721 let range = s.display_range(&display_map).sorted();
19722 let mut start = range.start.to_point(&display_map);
19723 let mut end = range.end.to_point(&display_map);
19724 start.column = 0;
19725 end.column = buffer.line_len(MultiBufferRow(end.row));
19726 start..end
19727 })
19728 .collect::<Vec<_>>();
19729
19730 self.unfold_ranges(&ranges, true, true, cx);
19731 } else {
19732 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19733 let buffer_ids = self
19734 .selections
19735 .disjoint_anchor_ranges()
19736 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19737 .collect::<HashSet<_>>();
19738 for buffer_id in buffer_ids {
19739 self.unfold_buffer(buffer_id, cx);
19740 }
19741 }
19742 }
19743
19744 pub fn unfold_recursive(
19745 &mut self,
19746 _: &UnfoldRecursive,
19747 _window: &mut Window,
19748 cx: &mut Context<Self>,
19749 ) {
19750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19751 let selections = self.selections.all::<Point>(&display_map);
19752 let ranges = selections
19753 .iter()
19754 .map(|s| {
19755 let mut range = s.display_range(&display_map).sorted();
19756 *range.start.column_mut() = 0;
19757 *range.end.column_mut() = display_map.line_len(range.end.row());
19758 let start = range.start.to_point(&display_map);
19759 let end = range.end.to_point(&display_map);
19760 start..end
19761 })
19762 .collect::<Vec<_>>();
19763
19764 self.unfold_ranges(&ranges, true, true, cx);
19765 }
19766
19767 pub fn unfold_at(
19768 &mut self,
19769 buffer_row: MultiBufferRow,
19770 _window: &mut Window,
19771 cx: &mut Context<Self>,
19772 ) {
19773 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19774
19775 let intersection_range = Point::new(buffer_row.0, 0)
19776 ..Point::new(
19777 buffer_row.0,
19778 display_map.buffer_snapshot().line_len(buffer_row),
19779 );
19780
19781 let autoscroll = self
19782 .selections
19783 .all::<Point>(&display_map)
19784 .iter()
19785 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19786
19787 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19788 }
19789
19790 pub fn unfold_all(
19791 &mut self,
19792 _: &actions::UnfoldAll,
19793 _window: &mut Window,
19794 cx: &mut Context<Self>,
19795 ) {
19796 if self.buffer.read(cx).is_singleton() {
19797 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19798 self.unfold_ranges(
19799 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19800 true,
19801 true,
19802 cx,
19803 );
19804 } else {
19805 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19806 editor
19807 .update(cx, |editor, cx| {
19808 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19809 editor.unfold_buffer(buffer_id, cx);
19810 }
19811 })
19812 .ok();
19813 });
19814 }
19815 cx.emit(SearchEvent::ResultsCollapsedChanged(
19816 CollapseDirection::Expanded,
19817 ));
19818 }
19819
19820 pub fn fold_selected_ranges(
19821 &mut self,
19822 _: &FoldSelectedRanges,
19823 window: &mut Window,
19824 cx: &mut Context<Self>,
19825 ) {
19826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19827 let selections = self.selections.all_adjusted(&display_map);
19828 let ranges = selections
19829 .into_iter()
19830 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19831 .collect::<Vec<_>>();
19832 self.fold_creases(ranges, true, window, cx);
19833 }
19834
19835 pub fn fold_ranges<T: ToOffset + Clone>(
19836 &mut self,
19837 ranges: Vec<Range<T>>,
19838 auto_scroll: bool,
19839 window: &mut Window,
19840 cx: &mut Context<Self>,
19841 ) {
19842 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19843 let ranges = ranges
19844 .into_iter()
19845 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19846 .collect::<Vec<_>>();
19847 self.fold_creases(ranges, auto_scroll, window, cx);
19848 }
19849
19850 pub fn fold_creases<T: ToOffset + Clone>(
19851 &mut self,
19852 creases: Vec<Crease<T>>,
19853 auto_scroll: bool,
19854 _window: &mut Window,
19855 cx: &mut Context<Self>,
19856 ) {
19857 if creases.is_empty() {
19858 return;
19859 }
19860
19861 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19862
19863 if auto_scroll {
19864 self.request_autoscroll(Autoscroll::fit(), cx);
19865 }
19866
19867 cx.notify();
19868
19869 self.scrollbar_marker_state.dirty = true;
19870 self.folds_did_change(cx);
19871 }
19872
19873 /// Removes any folds whose ranges intersect any of the given ranges.
19874 pub fn unfold_ranges<T: ToOffset + Clone>(
19875 &mut self,
19876 ranges: &[Range<T>],
19877 inclusive: bool,
19878 auto_scroll: bool,
19879 cx: &mut Context<Self>,
19880 ) {
19881 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19882 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19883 });
19884 self.folds_did_change(cx);
19885 }
19886
19887 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19888 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19889 return;
19890 }
19891
19892 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19893 self.display_map.update(cx, |display_map, cx| {
19894 display_map.fold_buffers([buffer_id], cx)
19895 });
19896
19897 let snapshot = self.display_snapshot(cx);
19898 self.selections.change_with(&snapshot, |selections| {
19899 selections.remove_selections_from_buffer(buffer_id);
19900 });
19901
19902 cx.emit(EditorEvent::BufferFoldToggled {
19903 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19904 folded: true,
19905 });
19906 cx.notify();
19907 }
19908
19909 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19910 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19911 return;
19912 }
19913 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19914 self.display_map.update(cx, |display_map, cx| {
19915 display_map.unfold_buffers([buffer_id], cx);
19916 });
19917 cx.emit(EditorEvent::BufferFoldToggled {
19918 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19919 folded: false,
19920 });
19921 cx.notify();
19922 }
19923
19924 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19925 self.display_map.read(cx).is_buffer_folded(buffer)
19926 }
19927
19928 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19929 self.display_map.read(cx).folded_buffers()
19930 }
19931
19932 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19933 self.display_map.update(cx, |display_map, cx| {
19934 display_map.disable_header_for_buffer(buffer_id, cx);
19935 });
19936 cx.notify();
19937 }
19938
19939 /// Removes any folds with the given ranges.
19940 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19941 &mut self,
19942 ranges: &[Range<T>],
19943 type_id: TypeId,
19944 auto_scroll: bool,
19945 cx: &mut Context<Self>,
19946 ) {
19947 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19948 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19949 });
19950 self.folds_did_change(cx);
19951 }
19952
19953 fn remove_folds_with<T: ToOffset + Clone>(
19954 &mut self,
19955 ranges: &[Range<T>],
19956 auto_scroll: bool,
19957 cx: &mut Context<Self>,
19958 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19959 ) {
19960 if ranges.is_empty() {
19961 return;
19962 }
19963
19964 let mut buffers_affected = HashSet::default();
19965 let multi_buffer = self.buffer().read(cx);
19966 for range in ranges {
19967 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19968 buffers_affected.insert(buffer.read(cx).remote_id());
19969 };
19970 }
19971
19972 self.display_map.update(cx, update);
19973
19974 if auto_scroll {
19975 self.request_autoscroll(Autoscroll::fit(), cx);
19976 }
19977
19978 cx.notify();
19979 self.scrollbar_marker_state.dirty = true;
19980 self.active_indent_guides_state.dirty = true;
19981 }
19982
19983 pub fn update_renderer_widths(
19984 &mut self,
19985 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19986 cx: &mut Context<Self>,
19987 ) -> bool {
19988 self.display_map
19989 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19990 }
19991
19992 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19993 self.display_map.read(cx).fold_placeholder.clone()
19994 }
19995
19996 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19997 self.buffer.update(cx, |buffer, cx| {
19998 buffer.set_all_diff_hunks_expanded(cx);
19999 });
20000 }
20001
20002 pub fn expand_all_diff_hunks(
20003 &mut self,
20004 _: &ExpandAllDiffHunks,
20005 _window: &mut Window,
20006 cx: &mut Context<Self>,
20007 ) {
20008 self.buffer.update(cx, |buffer, cx| {
20009 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20010 });
20011 }
20012
20013 pub fn collapse_all_diff_hunks(
20014 &mut self,
20015 _: &CollapseAllDiffHunks,
20016 _window: &mut Window,
20017 cx: &mut Context<Self>,
20018 ) {
20019 self.buffer.update(cx, |buffer, cx| {
20020 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20021 });
20022 }
20023
20024 pub fn toggle_selected_diff_hunks(
20025 &mut self,
20026 _: &ToggleSelectedDiffHunks,
20027 _window: &mut Window,
20028 cx: &mut Context<Self>,
20029 ) {
20030 let ranges: Vec<_> = self
20031 .selections
20032 .disjoint_anchors()
20033 .iter()
20034 .map(|s| s.range())
20035 .collect();
20036 self.toggle_diff_hunks_in_ranges(ranges, cx);
20037 }
20038
20039 pub fn diff_hunks_in_ranges<'a>(
20040 &'a self,
20041 ranges: &'a [Range<Anchor>],
20042 buffer: &'a MultiBufferSnapshot,
20043 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20044 ranges.iter().flat_map(move |range| {
20045 let end_excerpt_id = range.end.excerpt_id;
20046 let range = range.to_point(buffer);
20047 let mut peek_end = range.end;
20048 if range.end.row < buffer.max_row().0 {
20049 peek_end = Point::new(range.end.row + 1, 0);
20050 }
20051 buffer
20052 .diff_hunks_in_range(range.start..peek_end)
20053 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20054 })
20055 }
20056
20057 pub fn has_stageable_diff_hunks_in_ranges(
20058 &self,
20059 ranges: &[Range<Anchor>],
20060 snapshot: &MultiBufferSnapshot,
20061 ) -> bool {
20062 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20063 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20064 }
20065
20066 pub fn toggle_staged_selected_diff_hunks(
20067 &mut self,
20068 _: &::git::ToggleStaged,
20069 _: &mut Window,
20070 cx: &mut Context<Self>,
20071 ) {
20072 let snapshot = self.buffer.read(cx).snapshot(cx);
20073 let ranges: Vec<_> = self
20074 .selections
20075 .disjoint_anchors()
20076 .iter()
20077 .map(|s| s.range())
20078 .collect();
20079 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20080 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20081 }
20082
20083 pub fn set_render_diff_hunk_controls(
20084 &mut self,
20085 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20086 cx: &mut Context<Self>,
20087 ) {
20088 self.render_diff_hunk_controls = render_diff_hunk_controls;
20089 cx.notify();
20090 }
20091
20092 pub fn stage_and_next(
20093 &mut self,
20094 _: &::git::StageAndNext,
20095 window: &mut Window,
20096 cx: &mut Context<Self>,
20097 ) {
20098 self.do_stage_or_unstage_and_next(true, window, cx);
20099 }
20100
20101 pub fn unstage_and_next(
20102 &mut self,
20103 _: &::git::UnstageAndNext,
20104 window: &mut Window,
20105 cx: &mut Context<Self>,
20106 ) {
20107 self.do_stage_or_unstage_and_next(false, window, cx);
20108 }
20109
20110 pub fn stage_or_unstage_diff_hunks(
20111 &mut self,
20112 stage: bool,
20113 ranges: Vec<Range<Anchor>>,
20114 cx: &mut Context<Self>,
20115 ) {
20116 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20117 cx.spawn(async move |this, cx| {
20118 task.await?;
20119 this.update(cx, |this, cx| {
20120 let snapshot = this.buffer.read(cx).snapshot(cx);
20121 let chunk_by = this
20122 .diff_hunks_in_ranges(&ranges, &snapshot)
20123 .chunk_by(|hunk| hunk.buffer_id);
20124 for (buffer_id, hunks) in &chunk_by {
20125 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20126 }
20127 })
20128 })
20129 .detach_and_log_err(cx);
20130 }
20131
20132 fn save_buffers_for_ranges_if_needed(
20133 &mut self,
20134 ranges: &[Range<Anchor>],
20135 cx: &mut Context<Editor>,
20136 ) -> Task<Result<()>> {
20137 let multibuffer = self.buffer.read(cx);
20138 let snapshot = multibuffer.read(cx);
20139 let buffer_ids: HashSet<_> = ranges
20140 .iter()
20141 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20142 .collect();
20143 drop(snapshot);
20144
20145 let mut buffers = HashSet::default();
20146 for buffer_id in buffer_ids {
20147 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20148 let buffer = buffer_entity.read(cx);
20149 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20150 {
20151 buffers.insert(buffer_entity);
20152 }
20153 }
20154 }
20155
20156 if let Some(project) = &self.project {
20157 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20158 } else {
20159 Task::ready(Ok(()))
20160 }
20161 }
20162
20163 fn do_stage_or_unstage_and_next(
20164 &mut self,
20165 stage: bool,
20166 window: &mut Window,
20167 cx: &mut Context<Self>,
20168 ) {
20169 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20170
20171 if ranges.iter().any(|range| range.start != range.end) {
20172 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20173 return;
20174 }
20175
20176 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20177 let snapshot = self.snapshot(window, cx);
20178 let position = self
20179 .selections
20180 .newest::<Point>(&snapshot.display_snapshot)
20181 .head();
20182 let mut row = snapshot
20183 .buffer_snapshot()
20184 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20185 .find(|hunk| hunk.row_range.start.0 > position.row)
20186 .map(|hunk| hunk.row_range.start);
20187
20188 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20189 // Outside of the project diff editor, wrap around to the beginning.
20190 if !all_diff_hunks_expanded {
20191 row = row.or_else(|| {
20192 snapshot
20193 .buffer_snapshot()
20194 .diff_hunks_in_range(Point::zero()..position)
20195 .find(|hunk| hunk.row_range.end.0 < position.row)
20196 .map(|hunk| hunk.row_range.start)
20197 });
20198 }
20199
20200 if let Some(row) = row {
20201 let destination = Point::new(row.0, 0);
20202 let autoscroll = Autoscroll::center();
20203
20204 self.unfold_ranges(&[destination..destination], false, false, cx);
20205 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20206 s.select_ranges([destination..destination]);
20207 });
20208 }
20209 }
20210
20211 fn do_stage_or_unstage(
20212 &self,
20213 stage: bool,
20214 buffer_id: BufferId,
20215 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20216 cx: &mut App,
20217 ) -> Option<()> {
20218 let project = self.project()?;
20219 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20220 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20221 let buffer_snapshot = buffer.read(cx).snapshot();
20222 let file_exists = buffer_snapshot
20223 .file()
20224 .is_some_and(|file| file.disk_state().exists());
20225 diff.update(cx, |diff, cx| {
20226 diff.stage_or_unstage_hunks(
20227 stage,
20228 &hunks
20229 .map(|hunk| buffer_diff::DiffHunk {
20230 buffer_range: hunk.buffer_range,
20231 // We don't need to pass in word diffs here because they're only used for rendering and
20232 // this function changes internal state
20233 base_word_diffs: Vec::default(),
20234 buffer_word_diffs: Vec::default(),
20235 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20236 ..hunk.diff_base_byte_range.end.0,
20237 secondary_status: hunk.status.secondary,
20238 range: Point::zero()..Point::zero(), // unused
20239 })
20240 .collect::<Vec<_>>(),
20241 &buffer_snapshot,
20242 file_exists,
20243 cx,
20244 )
20245 });
20246 None
20247 }
20248
20249 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20250 let ranges: Vec<_> = self
20251 .selections
20252 .disjoint_anchors()
20253 .iter()
20254 .map(|s| s.range())
20255 .collect();
20256 self.buffer
20257 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20258 }
20259
20260 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20261 self.buffer.update(cx, |buffer, cx| {
20262 let ranges = vec![Anchor::min()..Anchor::max()];
20263 if !buffer.all_diff_hunks_expanded()
20264 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20265 {
20266 buffer.collapse_diff_hunks(ranges, cx);
20267 true
20268 } else {
20269 false
20270 }
20271 })
20272 }
20273
20274 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20275 if self.buffer.read(cx).all_diff_hunks_expanded() {
20276 return true;
20277 }
20278 let ranges = vec![Anchor::min()..Anchor::max()];
20279 self.buffer
20280 .read(cx)
20281 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20282 }
20283
20284 fn toggle_diff_hunks_in_ranges(
20285 &mut self,
20286 ranges: Vec<Range<Anchor>>,
20287 cx: &mut Context<Editor>,
20288 ) {
20289 self.buffer.update(cx, |buffer, cx| {
20290 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20291 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20292 })
20293 }
20294
20295 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20296 self.buffer.update(cx, |buffer, cx| {
20297 let snapshot = buffer.snapshot(cx);
20298 let excerpt_id = range.end.excerpt_id;
20299 let point_range = range.to_point(&snapshot);
20300 let expand = !buffer.single_hunk_is_expanded(range, cx);
20301 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20302 })
20303 }
20304
20305 pub(crate) fn apply_all_diff_hunks(
20306 &mut self,
20307 _: &ApplyAllDiffHunks,
20308 window: &mut Window,
20309 cx: &mut Context<Self>,
20310 ) {
20311 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20312
20313 let buffers = self.buffer.read(cx).all_buffers();
20314 for branch_buffer in buffers {
20315 branch_buffer.update(cx, |branch_buffer, cx| {
20316 branch_buffer.merge_into_base(Vec::new(), cx);
20317 });
20318 }
20319
20320 if let Some(project) = self.project.clone() {
20321 self.save(
20322 SaveOptions {
20323 format: true,
20324 autosave: false,
20325 },
20326 project,
20327 window,
20328 cx,
20329 )
20330 .detach_and_log_err(cx);
20331 }
20332 }
20333
20334 pub(crate) fn apply_selected_diff_hunks(
20335 &mut self,
20336 _: &ApplyDiffHunk,
20337 window: &mut Window,
20338 cx: &mut Context<Self>,
20339 ) {
20340 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20341 let snapshot = self.snapshot(window, cx);
20342 let hunks = snapshot.hunks_for_ranges(
20343 self.selections
20344 .all(&snapshot.display_snapshot)
20345 .into_iter()
20346 .map(|selection| selection.range()),
20347 );
20348 let mut ranges_by_buffer = HashMap::default();
20349 self.transact(window, cx, |editor, _window, cx| {
20350 for hunk in hunks {
20351 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20352 ranges_by_buffer
20353 .entry(buffer.clone())
20354 .or_insert_with(Vec::new)
20355 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20356 }
20357 }
20358
20359 for (buffer, ranges) in ranges_by_buffer {
20360 buffer.update(cx, |buffer, cx| {
20361 buffer.merge_into_base(ranges, cx);
20362 });
20363 }
20364 });
20365
20366 if let Some(project) = self.project.clone() {
20367 self.save(
20368 SaveOptions {
20369 format: true,
20370 autosave: false,
20371 },
20372 project,
20373 window,
20374 cx,
20375 )
20376 .detach_and_log_err(cx);
20377 }
20378 }
20379
20380 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20381 if hovered != self.gutter_hovered {
20382 self.gutter_hovered = hovered;
20383 cx.notify();
20384 }
20385 }
20386
20387 pub fn insert_blocks(
20388 &mut self,
20389 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20390 autoscroll: Option<Autoscroll>,
20391 cx: &mut Context<Self>,
20392 ) -> Vec<CustomBlockId> {
20393 let blocks = self
20394 .display_map
20395 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20396 if let Some(autoscroll) = autoscroll {
20397 self.request_autoscroll(autoscroll, cx);
20398 }
20399 cx.notify();
20400 blocks
20401 }
20402
20403 pub fn resize_blocks(
20404 &mut self,
20405 heights: HashMap<CustomBlockId, u32>,
20406 autoscroll: Option<Autoscroll>,
20407 cx: &mut Context<Self>,
20408 ) {
20409 self.display_map
20410 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20411 if let Some(autoscroll) = autoscroll {
20412 self.request_autoscroll(autoscroll, cx);
20413 }
20414 cx.notify();
20415 }
20416
20417 pub fn replace_blocks(
20418 &mut self,
20419 renderers: HashMap<CustomBlockId, RenderBlock>,
20420 autoscroll: Option<Autoscroll>,
20421 cx: &mut Context<Self>,
20422 ) {
20423 self.display_map
20424 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20425 if let Some(autoscroll) = autoscroll {
20426 self.request_autoscroll(autoscroll, cx);
20427 }
20428 cx.notify();
20429 }
20430
20431 pub fn remove_blocks(
20432 &mut self,
20433 block_ids: HashSet<CustomBlockId>,
20434 autoscroll: Option<Autoscroll>,
20435 cx: &mut Context<Self>,
20436 ) {
20437 self.display_map.update(cx, |display_map, cx| {
20438 display_map.remove_blocks(block_ids, cx)
20439 });
20440 if let Some(autoscroll) = autoscroll {
20441 self.request_autoscroll(autoscroll, cx);
20442 }
20443 cx.notify();
20444 }
20445
20446 pub fn row_for_block(
20447 &self,
20448 block_id: CustomBlockId,
20449 cx: &mut Context<Self>,
20450 ) -> Option<DisplayRow> {
20451 self.display_map
20452 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20453 }
20454
20455 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20456 self.focused_block = Some(focused_block);
20457 }
20458
20459 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20460 self.focused_block.take()
20461 }
20462
20463 pub fn insert_creases(
20464 &mut self,
20465 creases: impl IntoIterator<Item = Crease<Anchor>>,
20466 cx: &mut Context<Self>,
20467 ) -> Vec<CreaseId> {
20468 self.display_map
20469 .update(cx, |map, cx| map.insert_creases(creases, cx))
20470 }
20471
20472 pub fn remove_creases(
20473 &mut self,
20474 ids: impl IntoIterator<Item = CreaseId>,
20475 cx: &mut Context<Self>,
20476 ) -> Vec<(CreaseId, Range<Anchor>)> {
20477 self.display_map
20478 .update(cx, |map, cx| map.remove_creases(ids, cx))
20479 }
20480
20481 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20482 self.display_map
20483 .update(cx, |map, cx| map.snapshot(cx))
20484 .longest_row()
20485 }
20486
20487 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20488 self.display_map
20489 .update(cx, |map, cx| map.snapshot(cx))
20490 .max_point()
20491 }
20492
20493 pub fn text(&self, cx: &App) -> String {
20494 self.buffer.read(cx).read(cx).text()
20495 }
20496
20497 pub fn is_empty(&self, cx: &App) -> bool {
20498 self.buffer.read(cx).read(cx).is_empty()
20499 }
20500
20501 pub fn text_option(&self, cx: &App) -> Option<String> {
20502 let text = self.text(cx);
20503 let text = text.trim();
20504
20505 if text.is_empty() {
20506 return None;
20507 }
20508
20509 Some(text.to_string())
20510 }
20511
20512 pub fn set_text(
20513 &mut self,
20514 text: impl Into<Arc<str>>,
20515 window: &mut Window,
20516 cx: &mut Context<Self>,
20517 ) {
20518 self.transact(window, cx, |this, _, cx| {
20519 this.buffer
20520 .read(cx)
20521 .as_singleton()
20522 .expect("you can only call set_text on editors for singleton buffers")
20523 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20524 });
20525 }
20526
20527 pub fn display_text(&self, cx: &mut App) -> String {
20528 self.display_map
20529 .update(cx, |map, cx| map.snapshot(cx))
20530 .text()
20531 }
20532
20533 fn create_minimap(
20534 &self,
20535 minimap_settings: MinimapSettings,
20536 window: &mut Window,
20537 cx: &mut Context<Self>,
20538 ) -> Option<Entity<Self>> {
20539 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20540 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20541 }
20542
20543 fn initialize_new_minimap(
20544 &self,
20545 minimap_settings: MinimapSettings,
20546 window: &mut Window,
20547 cx: &mut Context<Self>,
20548 ) -> Entity<Self> {
20549 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20550 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20551
20552 let mut minimap = Editor::new_internal(
20553 EditorMode::Minimap {
20554 parent: cx.weak_entity(),
20555 },
20556 self.buffer.clone(),
20557 None,
20558 Some(self.display_map.clone()),
20559 window,
20560 cx,
20561 );
20562 minimap.scroll_manager.clone_state(&self.scroll_manager);
20563 minimap.set_text_style_refinement(TextStyleRefinement {
20564 font_size: Some(MINIMAP_FONT_SIZE),
20565 font_weight: Some(MINIMAP_FONT_WEIGHT),
20566 font_family: Some(MINIMAP_FONT_FAMILY),
20567 ..Default::default()
20568 });
20569 minimap.update_minimap_configuration(minimap_settings, cx);
20570 cx.new(|_| minimap)
20571 }
20572
20573 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20574 let current_line_highlight = minimap_settings
20575 .current_line_highlight
20576 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20577 self.set_current_line_highlight(Some(current_line_highlight));
20578 }
20579
20580 pub fn minimap(&self) -> Option<&Entity<Self>> {
20581 self.minimap
20582 .as_ref()
20583 .filter(|_| self.minimap_visibility.visible())
20584 }
20585
20586 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20587 let mut wrap_guides = smallvec![];
20588
20589 if self.show_wrap_guides == Some(false) {
20590 return wrap_guides;
20591 }
20592
20593 let settings = self.buffer.read(cx).language_settings(cx);
20594 if settings.show_wrap_guides {
20595 match self.soft_wrap_mode(cx) {
20596 SoftWrap::Column(soft_wrap) => {
20597 wrap_guides.push((soft_wrap as usize, true));
20598 }
20599 SoftWrap::Bounded(soft_wrap) => {
20600 wrap_guides.push((soft_wrap as usize, true));
20601 }
20602 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20603 }
20604 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20605 }
20606
20607 wrap_guides
20608 }
20609
20610 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20611 let settings = self.buffer.read(cx).language_settings(cx);
20612 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20613 match mode {
20614 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20615 SoftWrap::None
20616 }
20617 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20618 language_settings::SoftWrap::PreferredLineLength => {
20619 SoftWrap::Column(settings.preferred_line_length)
20620 }
20621 language_settings::SoftWrap::Bounded => {
20622 SoftWrap::Bounded(settings.preferred_line_length)
20623 }
20624 }
20625 }
20626
20627 pub fn set_soft_wrap_mode(
20628 &mut self,
20629 mode: language_settings::SoftWrap,
20630
20631 cx: &mut Context<Self>,
20632 ) {
20633 self.soft_wrap_mode_override = Some(mode);
20634 cx.notify();
20635 }
20636
20637 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20638 self.hard_wrap = hard_wrap;
20639 cx.notify();
20640 }
20641
20642 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20643 self.text_style_refinement = Some(style);
20644 }
20645
20646 /// called by the Element so we know what style we were most recently rendered with.
20647 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20648 // We intentionally do not inform the display map about the minimap style
20649 // so that wrapping is not recalculated and stays consistent for the editor
20650 // and its linked minimap.
20651 if !self.mode.is_minimap() {
20652 let font = style.text.font();
20653 let font_size = style.text.font_size.to_pixels(window.rem_size());
20654 let display_map = self
20655 .placeholder_display_map
20656 .as_ref()
20657 .filter(|_| self.is_empty(cx))
20658 .unwrap_or(&self.display_map);
20659
20660 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20661 }
20662 self.style = Some(style);
20663 }
20664
20665 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20666 if self.style.is_none() {
20667 self.style = Some(self.create_style(cx));
20668 }
20669 self.style.as_ref().unwrap()
20670 }
20671
20672 // Called by the element. This method is not designed to be called outside of the editor
20673 // element's layout code because it does not notify when rewrapping is computed synchronously.
20674 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20675 if self.is_empty(cx) {
20676 self.placeholder_display_map
20677 .as_ref()
20678 .map_or(false, |display_map| {
20679 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20680 })
20681 } else {
20682 self.display_map
20683 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20684 }
20685 }
20686
20687 pub fn set_soft_wrap(&mut self) {
20688 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20689 }
20690
20691 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20692 if self.soft_wrap_mode_override.is_some() {
20693 self.soft_wrap_mode_override.take();
20694 } else {
20695 let soft_wrap = match self.soft_wrap_mode(cx) {
20696 SoftWrap::GitDiff => return,
20697 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20698 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20699 language_settings::SoftWrap::None
20700 }
20701 };
20702 self.soft_wrap_mode_override = Some(soft_wrap);
20703 }
20704 cx.notify();
20705 }
20706
20707 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20708 let Some(workspace) = self.workspace() else {
20709 return;
20710 };
20711 let fs = workspace.read(cx).app_state().fs.clone();
20712 let current_show = TabBarSettings::get_global(cx).show;
20713 update_settings_file(fs, cx, move |setting, _| {
20714 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20715 });
20716 }
20717
20718 pub fn toggle_indent_guides(
20719 &mut self,
20720 _: &ToggleIndentGuides,
20721 _: &mut Window,
20722 cx: &mut Context<Self>,
20723 ) {
20724 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20725 self.buffer
20726 .read(cx)
20727 .language_settings(cx)
20728 .indent_guides
20729 .enabled
20730 });
20731 self.show_indent_guides = Some(!currently_enabled);
20732 cx.notify();
20733 }
20734
20735 fn should_show_indent_guides(&self) -> Option<bool> {
20736 self.show_indent_guides
20737 }
20738
20739 pub fn disable_indent_guides_for_buffer(
20740 &mut self,
20741 buffer_id: BufferId,
20742 cx: &mut Context<Self>,
20743 ) {
20744 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20745 cx.notify();
20746 }
20747
20748 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20749 self.buffers_with_disabled_indent_guides
20750 .contains(&buffer_id)
20751 }
20752
20753 pub fn toggle_line_numbers(
20754 &mut self,
20755 _: &ToggleLineNumbers,
20756 _: &mut Window,
20757 cx: &mut Context<Self>,
20758 ) {
20759 let mut editor_settings = EditorSettings::get_global(cx).clone();
20760 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20761 EditorSettings::override_global(editor_settings, cx);
20762 }
20763
20764 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20765 if let Some(show_line_numbers) = self.show_line_numbers {
20766 return show_line_numbers;
20767 }
20768 EditorSettings::get_global(cx).gutter.line_numbers
20769 }
20770
20771 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20772 match (
20773 self.use_relative_line_numbers,
20774 EditorSettings::get_global(cx).relative_line_numbers,
20775 ) {
20776 (None, setting) => setting,
20777 (Some(false), _) => RelativeLineNumbers::Disabled,
20778 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20779 (Some(true), _) => RelativeLineNumbers::Enabled,
20780 }
20781 }
20782
20783 pub fn toggle_relative_line_numbers(
20784 &mut self,
20785 _: &ToggleRelativeLineNumbers,
20786 _: &mut Window,
20787 cx: &mut Context<Self>,
20788 ) {
20789 let is_relative = self.relative_line_numbers(cx);
20790 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20791 }
20792
20793 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20794 self.use_relative_line_numbers = is_relative;
20795 cx.notify();
20796 }
20797
20798 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20799 self.show_gutter = show_gutter;
20800 cx.notify();
20801 }
20802
20803 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20804 self.show_scrollbars = ScrollbarAxes {
20805 horizontal: show,
20806 vertical: show,
20807 };
20808 cx.notify();
20809 }
20810
20811 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20812 self.show_scrollbars.vertical = show;
20813 cx.notify();
20814 }
20815
20816 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20817 self.show_scrollbars.horizontal = show;
20818 cx.notify();
20819 }
20820
20821 pub fn set_minimap_visibility(
20822 &mut self,
20823 minimap_visibility: MinimapVisibility,
20824 window: &mut Window,
20825 cx: &mut Context<Self>,
20826 ) {
20827 if self.minimap_visibility != minimap_visibility {
20828 if minimap_visibility.visible() && self.minimap.is_none() {
20829 let minimap_settings = EditorSettings::get_global(cx).minimap;
20830 self.minimap =
20831 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20832 }
20833 self.minimap_visibility = minimap_visibility;
20834 cx.notify();
20835 }
20836 }
20837
20838 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20839 self.set_show_scrollbars(false, cx);
20840 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20841 }
20842
20843 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20844 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20845 }
20846
20847 /// Normally the text in full mode and auto height editors is padded on the
20848 /// left side by roughly half a character width for improved hit testing.
20849 ///
20850 /// Use this method to disable this for cases where this is not wanted (e.g.
20851 /// if you want to align the editor text with some other text above or below)
20852 /// or if you want to add this padding to single-line editors.
20853 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20854 self.offset_content = offset_content;
20855 cx.notify();
20856 }
20857
20858 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20859 self.show_line_numbers = Some(show_line_numbers);
20860 cx.notify();
20861 }
20862
20863 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20864 self.disable_expand_excerpt_buttons = true;
20865 cx.notify();
20866 }
20867
20868 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
20869 self.delegate_expand_excerpts = delegate;
20870 }
20871
20872 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20873 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20874 cx.notify();
20875 }
20876
20877 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20878 self.show_code_actions = Some(show_code_actions);
20879 cx.notify();
20880 }
20881
20882 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20883 self.show_runnables = Some(show_runnables);
20884 cx.notify();
20885 }
20886
20887 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20888 self.show_breakpoints = Some(show_breakpoints);
20889 cx.notify();
20890 }
20891
20892 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
20893 self.show_diff_review_button = show;
20894 cx.notify();
20895 }
20896
20897 pub fn show_diff_review_button(&self) -> bool {
20898 self.show_diff_review_button
20899 }
20900
20901 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20902 if self.display_map.read(cx).masked != masked {
20903 self.display_map.update(cx, |map, _| map.masked = masked);
20904 }
20905 cx.notify()
20906 }
20907
20908 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20909 self.show_wrap_guides = Some(show_wrap_guides);
20910 cx.notify();
20911 }
20912
20913 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20914 self.show_indent_guides = Some(show_indent_guides);
20915 cx.notify();
20916 }
20917
20918 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20919 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20920 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20921 && let Some(dir) = file.abs_path(cx).parent()
20922 {
20923 return Some(dir.to_owned());
20924 }
20925 }
20926
20927 None
20928 }
20929
20930 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20931 self.active_excerpt(cx)?
20932 .1
20933 .read(cx)
20934 .file()
20935 .and_then(|f| f.as_local())
20936 }
20937
20938 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20939 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20940 let buffer = buffer.read(cx);
20941 if let Some(project_path) = buffer.project_path(cx) {
20942 let project = self.project()?.read(cx);
20943 project.absolute_path(&project_path, cx)
20944 } else {
20945 buffer
20946 .file()
20947 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20948 }
20949 })
20950 }
20951
20952 pub fn reveal_in_finder(
20953 &mut self,
20954 _: &RevealInFileManager,
20955 _window: &mut Window,
20956 cx: &mut Context<Self>,
20957 ) {
20958 if let Some(target) = self.target_file(cx) {
20959 cx.reveal_path(&target.abs_path(cx));
20960 }
20961 }
20962
20963 pub fn copy_path(
20964 &mut self,
20965 _: &zed_actions::workspace::CopyPath,
20966 _window: &mut Window,
20967 cx: &mut Context<Self>,
20968 ) {
20969 if let Some(path) = self.target_file_abs_path(cx)
20970 && let Some(path) = path.to_str()
20971 {
20972 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20973 } else {
20974 cx.propagate();
20975 }
20976 }
20977
20978 pub fn copy_relative_path(
20979 &mut self,
20980 _: &zed_actions::workspace::CopyRelativePath,
20981 _window: &mut Window,
20982 cx: &mut Context<Self>,
20983 ) {
20984 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20985 let project = self.project()?.read(cx);
20986 let path = buffer.read(cx).file()?.path();
20987 let path = path.display(project.path_style(cx));
20988 Some(path)
20989 }) {
20990 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20991 } else {
20992 cx.propagate();
20993 }
20994 }
20995
20996 /// Returns the project path for the editor's buffer, if any buffer is
20997 /// opened in the editor.
20998 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20999 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
21000 buffer.read(cx).project_path(cx)
21001 } else {
21002 None
21003 }
21004 }
21005
21006 // Returns true if the editor handled a go-to-line request
21007 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
21008 maybe!({
21009 let breakpoint_store = self.breakpoint_store.as_ref()?;
21010
21011 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
21012 else {
21013 self.clear_row_highlights::<ActiveDebugLine>();
21014 return None;
21015 };
21016
21017 let position = active_stack_frame.position;
21018 let buffer_id = position.buffer_id?;
21019 let snapshot = self
21020 .project
21021 .as_ref()?
21022 .read(cx)
21023 .buffer_for_id(buffer_id, cx)?
21024 .read(cx)
21025 .snapshot();
21026
21027 let mut handled = false;
21028 for (id, ExcerptRange { context, .. }) in
21029 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
21030 {
21031 if context.start.cmp(&position, &snapshot).is_ge()
21032 || context.end.cmp(&position, &snapshot).is_lt()
21033 {
21034 continue;
21035 }
21036 let snapshot = self.buffer.read(cx).snapshot(cx);
21037 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
21038
21039 handled = true;
21040 self.clear_row_highlights::<ActiveDebugLine>();
21041
21042 self.go_to_line::<ActiveDebugLine>(
21043 multibuffer_anchor,
21044 Some(cx.theme().colors().editor_debugger_active_line_background),
21045 window,
21046 cx,
21047 );
21048
21049 cx.notify();
21050 }
21051
21052 handled.then_some(())
21053 })
21054 .is_some()
21055 }
21056
21057 pub fn copy_file_name_without_extension(
21058 &mut self,
21059 _: &CopyFileNameWithoutExtension,
21060 _: &mut Window,
21061 cx: &mut Context<Self>,
21062 ) {
21063 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21064 let file = buffer.read(cx).file()?;
21065 file.path().file_stem()
21066 }) {
21067 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
21068 }
21069 }
21070
21071 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
21072 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21073 let file = buffer.read(cx).file()?;
21074 Some(file.file_name(cx))
21075 }) {
21076 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
21077 }
21078 }
21079
21080 pub fn toggle_git_blame(
21081 &mut self,
21082 _: &::git::Blame,
21083 window: &mut Window,
21084 cx: &mut Context<Self>,
21085 ) {
21086 self.show_git_blame_gutter = !self.show_git_blame_gutter;
21087
21088 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
21089 self.start_git_blame(true, window, cx);
21090 }
21091
21092 cx.notify();
21093 }
21094
21095 pub fn toggle_git_blame_inline(
21096 &mut self,
21097 _: &ToggleGitBlameInline,
21098 window: &mut Window,
21099 cx: &mut Context<Self>,
21100 ) {
21101 self.toggle_git_blame_inline_internal(true, window, cx);
21102 cx.notify();
21103 }
21104
21105 pub fn open_git_blame_commit(
21106 &mut self,
21107 _: &OpenGitBlameCommit,
21108 window: &mut Window,
21109 cx: &mut Context<Self>,
21110 ) {
21111 self.open_git_blame_commit_internal(window, cx);
21112 }
21113
21114 fn open_git_blame_commit_internal(
21115 &mut self,
21116 window: &mut Window,
21117 cx: &mut Context<Self>,
21118 ) -> Option<()> {
21119 let blame = self.blame.as_ref()?;
21120 let snapshot = self.snapshot(window, cx);
21121 let cursor = self
21122 .selections
21123 .newest::<Point>(&snapshot.display_snapshot)
21124 .head();
21125 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
21126 let (_, blame_entry) = blame
21127 .update(cx, |blame, cx| {
21128 blame
21129 .blame_for_rows(
21130 &[RowInfo {
21131 buffer_id: Some(buffer.remote_id()),
21132 buffer_row: Some(point.row),
21133 ..Default::default()
21134 }],
21135 cx,
21136 )
21137 .next()
21138 })
21139 .flatten()?;
21140 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21141 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
21142 let workspace = self.workspace()?.downgrade();
21143 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
21144 None
21145 }
21146
21147 pub fn git_blame_inline_enabled(&self) -> bool {
21148 self.git_blame_inline_enabled
21149 }
21150
21151 pub fn toggle_selection_menu(
21152 &mut self,
21153 _: &ToggleSelectionMenu,
21154 _: &mut Window,
21155 cx: &mut Context<Self>,
21156 ) {
21157 self.show_selection_menu = self
21158 .show_selection_menu
21159 .map(|show_selections_menu| !show_selections_menu)
21160 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
21161
21162 cx.notify();
21163 }
21164
21165 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
21166 self.show_selection_menu
21167 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
21168 }
21169
21170 fn start_git_blame(
21171 &mut self,
21172 user_triggered: bool,
21173 window: &mut Window,
21174 cx: &mut Context<Self>,
21175 ) {
21176 if let Some(project) = self.project() {
21177 if let Some(buffer) = self.buffer().read(cx).as_singleton()
21178 && buffer.read(cx).file().is_none()
21179 {
21180 return;
21181 }
21182
21183 let focused = self.focus_handle(cx).contains_focused(window, cx);
21184
21185 let project = project.clone();
21186 let blame = cx
21187 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
21188 self.blame_subscription =
21189 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
21190 self.blame = Some(blame);
21191 }
21192 }
21193
21194 fn toggle_git_blame_inline_internal(
21195 &mut self,
21196 user_triggered: bool,
21197 window: &mut Window,
21198 cx: &mut Context<Self>,
21199 ) {
21200 if self.git_blame_inline_enabled {
21201 self.git_blame_inline_enabled = false;
21202 self.show_git_blame_inline = false;
21203 self.show_git_blame_inline_delay_task.take();
21204 } else {
21205 self.git_blame_inline_enabled = true;
21206 self.start_git_blame_inline(user_triggered, window, cx);
21207 }
21208
21209 cx.notify();
21210 }
21211
21212 fn start_git_blame_inline(
21213 &mut self,
21214 user_triggered: bool,
21215 window: &mut Window,
21216 cx: &mut Context<Self>,
21217 ) {
21218 self.start_git_blame(user_triggered, window, cx);
21219
21220 if ProjectSettings::get_global(cx)
21221 .git
21222 .inline_blame_delay()
21223 .is_some()
21224 {
21225 self.start_inline_blame_timer(window, cx);
21226 } else {
21227 self.show_git_blame_inline = true
21228 }
21229 }
21230
21231 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
21232 self.blame.as_ref()
21233 }
21234
21235 pub fn show_git_blame_gutter(&self) -> bool {
21236 self.show_git_blame_gutter
21237 }
21238
21239 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
21240 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
21241 }
21242
21243 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
21244 self.show_git_blame_inline
21245 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
21246 && !self.newest_selection_head_on_empty_line(cx)
21247 && self.has_blame_entries(cx)
21248 }
21249
21250 fn has_blame_entries(&self, cx: &App) -> bool {
21251 self.blame()
21252 .is_some_and(|blame| blame.read(cx).has_generated_entries())
21253 }
21254
21255 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
21256 let cursor_anchor = self.selections.newest_anchor().head();
21257
21258 let snapshot = self.buffer.read(cx).snapshot(cx);
21259 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
21260
21261 snapshot.line_len(buffer_row) == 0
21262 }
21263
21264 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
21265 let buffer_and_selection = maybe!({
21266 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21267 let selection_range = selection.range();
21268
21269 let multi_buffer = self.buffer().read(cx);
21270 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21271 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21272
21273 let (buffer, range, _) = if selection.reversed {
21274 buffer_ranges.first()
21275 } else {
21276 buffer_ranges.last()
21277 }?;
21278
21279 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21280 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21281
21282 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21283 let selection = start_row_in_buffer..end_row_in_buffer;
21284
21285 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21286 };
21287
21288 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21289
21290 Some((
21291 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21292 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, Bias::Left, buffer)
21293 ..buffer_diff_snapshot.row_to_base_text_row(
21294 end_row_in_buffer,
21295 Bias::Left,
21296 buffer,
21297 ),
21298 ))
21299 });
21300
21301 let Some((buffer, selection)) = buffer_and_selection else {
21302 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21303 };
21304
21305 let Some(project) = self.project() else {
21306 return Task::ready(Err(anyhow!("editor does not have project")));
21307 };
21308
21309 project.update(cx, |project, cx| {
21310 project.get_permalink_to_line(&buffer, selection, cx)
21311 })
21312 }
21313
21314 pub fn copy_permalink_to_line(
21315 &mut self,
21316 _: &CopyPermalinkToLine,
21317 window: &mut Window,
21318 cx: &mut Context<Self>,
21319 ) {
21320 let permalink_task = self.get_permalink_to_line(cx);
21321 let workspace = self.workspace();
21322
21323 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21324 Ok(permalink) => {
21325 cx.update(|_, cx| {
21326 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21327 })
21328 .ok();
21329 }
21330 Err(err) => {
21331 let message = format!("Failed to copy permalink: {err}");
21332
21333 anyhow::Result::<()>::Err(err).log_err();
21334
21335 if let Some(workspace) = workspace {
21336 workspace
21337 .update_in(cx, |workspace, _, cx| {
21338 struct CopyPermalinkToLine;
21339
21340 workspace.show_toast(
21341 Toast::new(
21342 NotificationId::unique::<CopyPermalinkToLine>(),
21343 message,
21344 ),
21345 cx,
21346 )
21347 })
21348 .ok();
21349 }
21350 }
21351 })
21352 .detach();
21353 }
21354
21355 pub fn copy_file_location(
21356 &mut self,
21357 _: &CopyFileLocation,
21358 _: &mut Window,
21359 cx: &mut Context<Self>,
21360 ) {
21361 let selection = self
21362 .selections
21363 .newest::<Point>(&self.display_snapshot(cx))
21364 .start
21365 .row
21366 + 1;
21367 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21368 let project = self.project()?.read(cx);
21369 let file = buffer.read(cx).file()?;
21370 let path = file.path().display(project.path_style(cx));
21371
21372 Some(format!("{path}:{selection}"))
21373 }) {
21374 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21375 }
21376 }
21377
21378 pub fn open_permalink_to_line(
21379 &mut self,
21380 _: &OpenPermalinkToLine,
21381 window: &mut Window,
21382 cx: &mut Context<Self>,
21383 ) {
21384 let permalink_task = self.get_permalink_to_line(cx);
21385 let workspace = self.workspace();
21386
21387 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21388 Ok(permalink) => {
21389 cx.update(|_, cx| {
21390 cx.open_url(permalink.as_ref());
21391 })
21392 .ok();
21393 }
21394 Err(err) => {
21395 let message = format!("Failed to open permalink: {err}");
21396
21397 anyhow::Result::<()>::Err(err).log_err();
21398
21399 if let Some(workspace) = workspace {
21400 workspace.update(cx, |workspace, cx| {
21401 struct OpenPermalinkToLine;
21402
21403 workspace.show_toast(
21404 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
21405 cx,
21406 )
21407 });
21408 }
21409 }
21410 })
21411 .detach();
21412 }
21413
21414 pub fn insert_uuid_v4(
21415 &mut self,
21416 _: &InsertUuidV4,
21417 window: &mut Window,
21418 cx: &mut Context<Self>,
21419 ) {
21420 self.insert_uuid(UuidVersion::V4, window, cx);
21421 }
21422
21423 pub fn insert_uuid_v7(
21424 &mut self,
21425 _: &InsertUuidV7,
21426 window: &mut Window,
21427 cx: &mut Context<Self>,
21428 ) {
21429 self.insert_uuid(UuidVersion::V7, window, cx);
21430 }
21431
21432 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21433 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21434 self.transact(window, cx, |this, window, cx| {
21435 let edits = this
21436 .selections
21437 .all::<Point>(&this.display_snapshot(cx))
21438 .into_iter()
21439 .map(|selection| {
21440 let uuid = match version {
21441 UuidVersion::V4 => uuid::Uuid::new_v4(),
21442 UuidVersion::V7 => uuid::Uuid::now_v7(),
21443 };
21444
21445 (selection.range(), uuid.to_string())
21446 });
21447 this.edit(edits, cx);
21448 this.refresh_edit_prediction(true, false, window, cx);
21449 });
21450 }
21451
21452 pub fn open_selections_in_multibuffer(
21453 &mut self,
21454 _: &OpenSelectionsInMultibuffer,
21455 window: &mut Window,
21456 cx: &mut Context<Self>,
21457 ) {
21458 let multibuffer = self.buffer.read(cx);
21459
21460 let Some(buffer) = multibuffer.as_singleton() else {
21461 return;
21462 };
21463
21464 let Some(workspace) = self.workspace() else {
21465 return;
21466 };
21467
21468 let title = multibuffer.title(cx).to_string();
21469
21470 let locations = self
21471 .selections
21472 .all_anchors(&self.display_snapshot(cx))
21473 .iter()
21474 .map(|selection| {
21475 (
21476 buffer.clone(),
21477 (selection.start.text_anchor..selection.end.text_anchor)
21478 .to_point(buffer.read(cx)),
21479 )
21480 })
21481 .into_group_map();
21482
21483 cx.spawn_in(window, async move |_, cx| {
21484 workspace.update_in(cx, |workspace, window, cx| {
21485 Self::open_locations_in_multibuffer(
21486 workspace,
21487 locations,
21488 format!("Selections for '{title}'"),
21489 false,
21490 false,
21491 MultibufferSelectionMode::All,
21492 window,
21493 cx,
21494 );
21495 })
21496 })
21497 .detach();
21498 }
21499
21500 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21501 /// last highlight added will be used.
21502 ///
21503 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21504 pub fn highlight_rows<T: 'static>(
21505 &mut self,
21506 range: Range<Anchor>,
21507 color: Hsla,
21508 options: RowHighlightOptions,
21509 cx: &mut Context<Self>,
21510 ) {
21511 let snapshot = self.buffer().read(cx).snapshot(cx);
21512 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21513 let ix = row_highlights.binary_search_by(|highlight| {
21514 Ordering::Equal
21515 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21516 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21517 });
21518
21519 if let Err(mut ix) = ix {
21520 let index = post_inc(&mut self.highlight_order);
21521
21522 // If this range intersects with the preceding highlight, then merge it with
21523 // the preceding highlight. Otherwise insert a new highlight.
21524 let mut merged = false;
21525 if ix > 0 {
21526 let prev_highlight = &mut row_highlights[ix - 1];
21527 if prev_highlight
21528 .range
21529 .end
21530 .cmp(&range.start, &snapshot)
21531 .is_ge()
21532 {
21533 ix -= 1;
21534 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21535 prev_highlight.range.end = range.end;
21536 }
21537 merged = true;
21538 prev_highlight.index = index;
21539 prev_highlight.color = color;
21540 prev_highlight.options = options;
21541 }
21542 }
21543
21544 if !merged {
21545 row_highlights.insert(
21546 ix,
21547 RowHighlight {
21548 range,
21549 index,
21550 color,
21551 options,
21552 type_id: TypeId::of::<T>(),
21553 },
21554 );
21555 }
21556
21557 // If any of the following highlights intersect with this one, merge them.
21558 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21559 let highlight = &row_highlights[ix];
21560 if next_highlight
21561 .range
21562 .start
21563 .cmp(&highlight.range.end, &snapshot)
21564 .is_le()
21565 {
21566 if next_highlight
21567 .range
21568 .end
21569 .cmp(&highlight.range.end, &snapshot)
21570 .is_gt()
21571 {
21572 row_highlights[ix].range.end = next_highlight.range.end;
21573 }
21574 row_highlights.remove(ix + 1);
21575 } else {
21576 break;
21577 }
21578 }
21579 }
21580 }
21581
21582 /// Remove any highlighted row ranges of the given type that intersect the
21583 /// given ranges.
21584 pub fn remove_highlighted_rows<T: 'static>(
21585 &mut self,
21586 ranges_to_remove: Vec<Range<Anchor>>,
21587 cx: &mut Context<Self>,
21588 ) {
21589 let snapshot = self.buffer().read(cx).snapshot(cx);
21590 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21591 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21592 row_highlights.retain(|highlight| {
21593 while let Some(range_to_remove) = ranges_to_remove.peek() {
21594 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21595 Ordering::Less | Ordering::Equal => {
21596 ranges_to_remove.next();
21597 }
21598 Ordering::Greater => {
21599 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21600 Ordering::Less | Ordering::Equal => {
21601 return false;
21602 }
21603 Ordering::Greater => break,
21604 }
21605 }
21606 }
21607 }
21608
21609 true
21610 })
21611 }
21612
21613 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21614 pub fn clear_row_highlights<T: 'static>(&mut self) {
21615 self.highlighted_rows.remove(&TypeId::of::<T>());
21616 }
21617
21618 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21619 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21620 self.highlighted_rows
21621 .get(&TypeId::of::<T>())
21622 .map_or(&[] as &[_], |vec| vec.as_slice())
21623 .iter()
21624 .map(|highlight| (highlight.range.clone(), highlight.color))
21625 }
21626
21627 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21628 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21629 /// Allows to ignore certain kinds of highlights.
21630 pub fn highlighted_display_rows(
21631 &self,
21632 window: &mut Window,
21633 cx: &mut App,
21634 ) -> BTreeMap<DisplayRow, LineHighlight> {
21635 let snapshot = self.snapshot(window, cx);
21636 let mut used_highlight_orders = HashMap::default();
21637 self.highlighted_rows
21638 .iter()
21639 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21640 .fold(
21641 BTreeMap::<DisplayRow, LineHighlight>::new(),
21642 |mut unique_rows, highlight| {
21643 let start = highlight.range.start.to_display_point(&snapshot);
21644 let end = highlight.range.end.to_display_point(&snapshot);
21645 let start_row = start.row().0;
21646 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21647 {
21648 end.row().0.saturating_sub(1)
21649 } else {
21650 end.row().0
21651 };
21652 for row in start_row..=end_row {
21653 let used_index =
21654 used_highlight_orders.entry(row).or_insert(highlight.index);
21655 if highlight.index >= *used_index {
21656 *used_index = highlight.index;
21657 unique_rows.insert(
21658 DisplayRow(row),
21659 LineHighlight {
21660 include_gutter: highlight.options.include_gutter,
21661 border: None,
21662 background: highlight.color.into(),
21663 type_id: Some(highlight.type_id),
21664 },
21665 );
21666 }
21667 }
21668 unique_rows
21669 },
21670 )
21671 }
21672
21673 pub fn highlighted_display_row_for_autoscroll(
21674 &self,
21675 snapshot: &DisplaySnapshot,
21676 ) -> Option<DisplayRow> {
21677 self.highlighted_rows
21678 .values()
21679 .flat_map(|highlighted_rows| highlighted_rows.iter())
21680 .filter_map(|highlight| {
21681 if highlight.options.autoscroll {
21682 Some(highlight.range.start.to_display_point(snapshot).row())
21683 } else {
21684 None
21685 }
21686 })
21687 .min()
21688 }
21689
21690 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21691 self.highlight_background::<SearchWithinRange>(
21692 ranges,
21693 |_, colors| colors.colors().editor_document_highlight_read_background,
21694 cx,
21695 )
21696 }
21697
21698 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21699 self.breadcrumb_header = Some(new_header);
21700 }
21701
21702 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21703 self.clear_background_highlights::<SearchWithinRange>(cx);
21704 }
21705
21706 pub fn highlight_background<T: 'static>(
21707 &mut self,
21708 ranges: &[Range<Anchor>],
21709 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21710 cx: &mut Context<Self>,
21711 ) {
21712 self.background_highlights.insert(
21713 HighlightKey::Type(TypeId::of::<T>()),
21714 (Arc::new(color_fetcher), Arc::from(ranges)),
21715 );
21716 self.scrollbar_marker_state.dirty = true;
21717 cx.notify();
21718 }
21719
21720 pub fn highlight_background_key<T: 'static>(
21721 &mut self,
21722 key: usize,
21723 ranges: &[Range<Anchor>],
21724 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21725 cx: &mut Context<Self>,
21726 ) {
21727 self.background_highlights.insert(
21728 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21729 (Arc::new(color_fetcher), Arc::from(ranges)),
21730 );
21731 self.scrollbar_marker_state.dirty = true;
21732 cx.notify();
21733 }
21734
21735 pub fn clear_background_highlights<T: 'static>(
21736 &mut self,
21737 cx: &mut Context<Self>,
21738 ) -> Option<BackgroundHighlight> {
21739 let text_highlights = self
21740 .background_highlights
21741 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21742 if !text_highlights.1.is_empty() {
21743 self.scrollbar_marker_state.dirty = true;
21744 cx.notify();
21745 }
21746 Some(text_highlights)
21747 }
21748
21749 pub fn highlight_gutter<T: 'static>(
21750 &mut self,
21751 ranges: impl Into<Vec<Range<Anchor>>>,
21752 color_fetcher: fn(&App) -> Hsla,
21753 cx: &mut Context<Self>,
21754 ) {
21755 self.gutter_highlights
21756 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21757 cx.notify();
21758 }
21759
21760 pub fn clear_gutter_highlights<T: 'static>(
21761 &mut self,
21762 cx: &mut Context<Self>,
21763 ) -> Option<GutterHighlight> {
21764 cx.notify();
21765 self.gutter_highlights.remove(&TypeId::of::<T>())
21766 }
21767
21768 pub fn insert_gutter_highlight<T: 'static>(
21769 &mut self,
21770 range: Range<Anchor>,
21771 color_fetcher: fn(&App) -> Hsla,
21772 cx: &mut Context<Self>,
21773 ) {
21774 let snapshot = self.buffer().read(cx).snapshot(cx);
21775 let mut highlights = self
21776 .gutter_highlights
21777 .remove(&TypeId::of::<T>())
21778 .map(|(_, highlights)| highlights)
21779 .unwrap_or_default();
21780 let ix = highlights.binary_search_by(|highlight| {
21781 Ordering::Equal
21782 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21783 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21784 });
21785 if let Err(ix) = ix {
21786 highlights.insert(ix, range);
21787 }
21788 self.gutter_highlights
21789 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21790 }
21791
21792 pub fn remove_gutter_highlights<T: 'static>(
21793 &mut self,
21794 ranges_to_remove: Vec<Range<Anchor>>,
21795 cx: &mut Context<Self>,
21796 ) {
21797 let snapshot = self.buffer().read(cx).snapshot(cx);
21798 let Some((color_fetcher, mut gutter_highlights)) =
21799 self.gutter_highlights.remove(&TypeId::of::<T>())
21800 else {
21801 return;
21802 };
21803 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21804 gutter_highlights.retain(|highlight| {
21805 while let Some(range_to_remove) = ranges_to_remove.peek() {
21806 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21807 Ordering::Less | Ordering::Equal => {
21808 ranges_to_remove.next();
21809 }
21810 Ordering::Greater => {
21811 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21812 Ordering::Less | Ordering::Equal => {
21813 return false;
21814 }
21815 Ordering::Greater => break,
21816 }
21817 }
21818 }
21819 }
21820
21821 true
21822 });
21823 self.gutter_highlights
21824 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21825 }
21826
21827 #[cfg(feature = "test-support")]
21828 pub fn all_text_highlights(
21829 &self,
21830 window: &mut Window,
21831 cx: &mut Context<Self>,
21832 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21833 let snapshot = self.snapshot(window, cx);
21834 self.display_map.update(cx, |display_map, _| {
21835 display_map
21836 .all_text_highlights()
21837 .map(|highlight| {
21838 let (style, ranges) = highlight.as_ref();
21839 (
21840 *style,
21841 ranges
21842 .iter()
21843 .map(|range| range.clone().to_display_points(&snapshot))
21844 .collect(),
21845 )
21846 })
21847 .collect()
21848 })
21849 }
21850
21851 #[cfg(feature = "test-support")]
21852 pub fn all_text_background_highlights(
21853 &self,
21854 window: &mut Window,
21855 cx: &mut Context<Self>,
21856 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21857 let snapshot = self.snapshot(window, cx);
21858 let buffer = &snapshot.buffer_snapshot();
21859 let start = buffer.anchor_before(MultiBufferOffset(0));
21860 let end = buffer.anchor_after(buffer.len());
21861 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21862 }
21863
21864 #[cfg(any(test, feature = "test-support"))]
21865 pub fn sorted_background_highlights_in_range(
21866 &self,
21867 search_range: Range<Anchor>,
21868 display_snapshot: &DisplaySnapshot,
21869 theme: &Theme,
21870 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21871 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21872 res.sort_by(|a, b| {
21873 a.0.start
21874 .cmp(&b.0.start)
21875 .then_with(|| a.0.end.cmp(&b.0.end))
21876 .then_with(|| a.1.cmp(&b.1))
21877 });
21878 res
21879 }
21880
21881 #[cfg(feature = "test-support")]
21882 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21883 let snapshot = self.buffer().read(cx).snapshot(cx);
21884
21885 let highlights = self
21886 .background_highlights
21887 .get(&HighlightKey::Type(TypeId::of::<
21888 items::BufferSearchHighlights,
21889 >()));
21890
21891 if let Some((_color, ranges)) = highlights {
21892 ranges
21893 .iter()
21894 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21895 .collect_vec()
21896 } else {
21897 vec![]
21898 }
21899 }
21900
21901 fn document_highlights_for_position<'a>(
21902 &'a self,
21903 position: Anchor,
21904 buffer: &'a MultiBufferSnapshot,
21905 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21906 let read_highlights = self
21907 .background_highlights
21908 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21909 .map(|h| &h.1);
21910 let write_highlights = self
21911 .background_highlights
21912 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21913 .map(|h| &h.1);
21914 let left_position = position.bias_left(buffer);
21915 let right_position = position.bias_right(buffer);
21916 read_highlights
21917 .into_iter()
21918 .chain(write_highlights)
21919 .flat_map(move |ranges| {
21920 let start_ix = match ranges.binary_search_by(|probe| {
21921 let cmp = probe.end.cmp(&left_position, buffer);
21922 if cmp.is_ge() {
21923 Ordering::Greater
21924 } else {
21925 Ordering::Less
21926 }
21927 }) {
21928 Ok(i) | Err(i) => i,
21929 };
21930
21931 ranges[start_ix..]
21932 .iter()
21933 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21934 })
21935 }
21936
21937 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21938 self.background_highlights
21939 .get(&HighlightKey::Type(TypeId::of::<T>()))
21940 .is_some_and(|(_, highlights)| !highlights.is_empty())
21941 }
21942
21943 /// Returns all background highlights for a given range.
21944 ///
21945 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21946 pub fn background_highlights_in_range(
21947 &self,
21948 search_range: Range<Anchor>,
21949 display_snapshot: &DisplaySnapshot,
21950 theme: &Theme,
21951 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21952 let mut results = Vec::new();
21953 for (color_fetcher, ranges) in self.background_highlights.values() {
21954 let start_ix = match ranges.binary_search_by(|probe| {
21955 let cmp = probe
21956 .end
21957 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21958 if cmp.is_gt() {
21959 Ordering::Greater
21960 } else {
21961 Ordering::Less
21962 }
21963 }) {
21964 Ok(i) | Err(i) => i,
21965 };
21966 for (index, range) in ranges[start_ix..].iter().enumerate() {
21967 if range
21968 .start
21969 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21970 .is_ge()
21971 {
21972 break;
21973 }
21974
21975 let color = color_fetcher(&(start_ix + index), theme);
21976 let start = range.start.to_display_point(display_snapshot);
21977 let end = range.end.to_display_point(display_snapshot);
21978 results.push((start..end, color))
21979 }
21980 }
21981 results
21982 }
21983
21984 pub fn gutter_highlights_in_range(
21985 &self,
21986 search_range: Range<Anchor>,
21987 display_snapshot: &DisplaySnapshot,
21988 cx: &App,
21989 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21990 let mut results = Vec::new();
21991 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21992 let color = color_fetcher(cx);
21993 let start_ix = match ranges.binary_search_by(|probe| {
21994 let cmp = probe
21995 .end
21996 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21997 if cmp.is_gt() {
21998 Ordering::Greater
21999 } else {
22000 Ordering::Less
22001 }
22002 }) {
22003 Ok(i) | Err(i) => i,
22004 };
22005 for range in &ranges[start_ix..] {
22006 if range
22007 .start
22008 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
22009 .is_ge()
22010 {
22011 break;
22012 }
22013
22014 let start = range.start.to_display_point(display_snapshot);
22015 let end = range.end.to_display_point(display_snapshot);
22016 results.push((start..end, color))
22017 }
22018 }
22019 results
22020 }
22021
22022 /// Get the text ranges corresponding to the redaction query
22023 pub fn redacted_ranges(
22024 &self,
22025 search_range: Range<Anchor>,
22026 display_snapshot: &DisplaySnapshot,
22027 cx: &App,
22028 ) -> Vec<Range<DisplayPoint>> {
22029 display_snapshot
22030 .buffer_snapshot()
22031 .redacted_ranges(search_range, |file| {
22032 if let Some(file) = file {
22033 file.is_private()
22034 && EditorSettings::get(
22035 Some(SettingsLocation {
22036 worktree_id: file.worktree_id(cx),
22037 path: file.path().as_ref(),
22038 }),
22039 cx,
22040 )
22041 .redact_private_values
22042 } else {
22043 false
22044 }
22045 })
22046 .map(|range| {
22047 range.start.to_display_point(display_snapshot)
22048 ..range.end.to_display_point(display_snapshot)
22049 })
22050 .collect()
22051 }
22052
22053 pub fn highlight_text_key<T: 'static>(
22054 &mut self,
22055 key: usize,
22056 ranges: Vec<Range<Anchor>>,
22057 style: HighlightStyle,
22058 merge: bool,
22059 cx: &mut Context<Self>,
22060 ) {
22061 self.display_map.update(cx, |map, cx| {
22062 map.highlight_text(
22063 HighlightKey::TypePlus(TypeId::of::<T>(), key),
22064 ranges,
22065 style,
22066 merge,
22067 cx,
22068 );
22069 });
22070 cx.notify();
22071 }
22072
22073 pub fn highlight_text<T: 'static>(
22074 &mut self,
22075 ranges: Vec<Range<Anchor>>,
22076 style: HighlightStyle,
22077 cx: &mut Context<Self>,
22078 ) {
22079 self.display_map.update(cx, |map, cx| {
22080 map.highlight_text(
22081 HighlightKey::Type(TypeId::of::<T>()),
22082 ranges,
22083 style,
22084 false,
22085 cx,
22086 )
22087 });
22088 cx.notify();
22089 }
22090
22091 pub fn text_highlights<'a, T: 'static>(
22092 &'a self,
22093 cx: &'a App,
22094 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
22095 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
22096 }
22097
22098 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
22099 let cleared = self
22100 .display_map
22101 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
22102 if cleared {
22103 cx.notify();
22104 }
22105 }
22106
22107 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
22108 (self.read_only(cx) || self.blink_manager.read(cx).visible())
22109 && self.focus_handle.is_focused(window)
22110 }
22111
22112 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
22113 self.show_cursor_when_unfocused = is_enabled;
22114 cx.notify();
22115 }
22116
22117 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
22118 cx.notify();
22119 }
22120
22121 fn on_debug_session_event(
22122 &mut self,
22123 _session: Entity<Session>,
22124 event: &SessionEvent,
22125 cx: &mut Context<Self>,
22126 ) {
22127 if let SessionEvent::InvalidateInlineValue = event {
22128 self.refresh_inline_values(cx);
22129 }
22130 }
22131
22132 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
22133 let Some(project) = self.project.clone() else {
22134 return;
22135 };
22136
22137 if !self.inline_value_cache.enabled {
22138 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
22139 self.splice_inlays(&inlays, Vec::new(), cx);
22140 return;
22141 }
22142
22143 let current_execution_position = self
22144 .highlighted_rows
22145 .get(&TypeId::of::<ActiveDebugLine>())
22146 .and_then(|lines| lines.last().map(|line| line.range.end));
22147
22148 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
22149 let inline_values = editor
22150 .update(cx, |editor, cx| {
22151 let Some(current_execution_position) = current_execution_position else {
22152 return Some(Task::ready(Ok(Vec::new())));
22153 };
22154
22155 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
22156 let snapshot = buffer.snapshot(cx);
22157
22158 let excerpt = snapshot.excerpt_containing(
22159 current_execution_position..current_execution_position,
22160 )?;
22161
22162 editor.buffer.read(cx).buffer(excerpt.buffer_id())
22163 })?;
22164
22165 let range =
22166 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
22167
22168 project.inline_values(buffer, range, cx)
22169 })
22170 .ok()
22171 .flatten()?
22172 .await
22173 .context("refreshing debugger inlays")
22174 .log_err()?;
22175
22176 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
22177
22178 for (buffer_id, inline_value) in inline_values
22179 .into_iter()
22180 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
22181 {
22182 buffer_inline_values
22183 .entry(buffer_id)
22184 .or_default()
22185 .push(inline_value);
22186 }
22187
22188 editor
22189 .update(cx, |editor, cx| {
22190 let snapshot = editor.buffer.read(cx).snapshot(cx);
22191 let mut new_inlays = Vec::default();
22192
22193 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
22194 let buffer_id = buffer_snapshot.remote_id();
22195 buffer_inline_values
22196 .get(&buffer_id)
22197 .into_iter()
22198 .flatten()
22199 .for_each(|hint| {
22200 let inlay = Inlay::debugger(
22201 post_inc(&mut editor.next_inlay_id),
22202 Anchor::in_buffer(excerpt_id, hint.position),
22203 hint.text(),
22204 );
22205 if !inlay.text().chars().contains(&'\n') {
22206 new_inlays.push(inlay);
22207 }
22208 });
22209 }
22210
22211 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
22212 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
22213
22214 editor.splice_inlays(&inlay_ids, new_inlays, cx);
22215 })
22216 .ok()?;
22217 Some(())
22218 });
22219 }
22220
22221 fn on_buffer_event(
22222 &mut self,
22223 multibuffer: &Entity<MultiBuffer>,
22224 event: &multi_buffer::Event,
22225 window: &mut Window,
22226 cx: &mut Context<Self>,
22227 ) {
22228 match event {
22229 multi_buffer::Event::Edited { edited_buffer } => {
22230 self.scrollbar_marker_state.dirty = true;
22231 self.active_indent_guides_state.dirty = true;
22232 self.refresh_active_diagnostics(cx);
22233 self.refresh_code_actions(window, cx);
22234 self.refresh_single_line_folds(window, cx);
22235 self.refresh_matching_bracket_highlights(window, cx);
22236 if self.has_active_edit_prediction() {
22237 self.update_visible_edit_prediction(window, cx);
22238 }
22239
22240 if let Some(buffer) = edited_buffer {
22241 if buffer.read(cx).file().is_none() {
22242 cx.emit(EditorEvent::TitleChanged);
22243 }
22244
22245 if self.project.is_some() {
22246 let buffer_id = buffer.read(cx).remote_id();
22247 self.register_buffer(buffer_id, cx);
22248 self.update_lsp_data(Some(buffer_id), window, cx);
22249 self.refresh_inlay_hints(
22250 InlayHintRefreshReason::BufferEdited(buffer_id),
22251 cx,
22252 );
22253 }
22254 }
22255
22256 cx.emit(EditorEvent::BufferEdited);
22257 cx.emit(SearchEvent::MatchesInvalidated);
22258
22259 let Some(project) = &self.project else { return };
22260 let (telemetry, is_via_ssh) = {
22261 let project = project.read(cx);
22262 let telemetry = project.client().telemetry().clone();
22263 let is_via_ssh = project.is_via_remote_server();
22264 (telemetry, is_via_ssh)
22265 };
22266 telemetry.log_edit_event("editor", is_via_ssh);
22267 }
22268 multi_buffer::Event::ExcerptsAdded {
22269 buffer,
22270 predecessor,
22271 excerpts,
22272 } => {
22273 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22274 let buffer_id = buffer.read(cx).remote_id();
22275 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22276 && let Some(project) = &self.project
22277 {
22278 update_uncommitted_diff_for_buffer(
22279 cx.entity(),
22280 project,
22281 [buffer.clone()],
22282 self.buffer.clone(),
22283 cx,
22284 )
22285 .detach();
22286 }
22287 self.update_lsp_data(Some(buffer_id), window, cx);
22288 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22289 self.colorize_brackets(false, cx);
22290 self.refresh_selected_text_highlights(true, window, cx);
22291 cx.emit(EditorEvent::ExcerptsAdded {
22292 buffer: buffer.clone(),
22293 predecessor: *predecessor,
22294 excerpts: excerpts.clone(),
22295 });
22296 }
22297 multi_buffer::Event::ExcerptsRemoved {
22298 ids,
22299 removed_buffer_ids,
22300 } => {
22301 if let Some(inlay_hints) = &mut self.inlay_hints {
22302 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22303 }
22304 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22305 for buffer_id in removed_buffer_ids {
22306 self.registered_buffers.remove(buffer_id);
22307 }
22308 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22309 cx.emit(EditorEvent::ExcerptsRemoved {
22310 ids: ids.clone(),
22311 removed_buffer_ids: removed_buffer_ids.clone(),
22312 });
22313 }
22314 multi_buffer::Event::ExcerptsEdited {
22315 excerpt_ids,
22316 buffer_ids,
22317 } => {
22318 self.display_map.update(cx, |map, cx| {
22319 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22320 });
22321 cx.emit(EditorEvent::ExcerptsEdited {
22322 ids: excerpt_ids.clone(),
22323 });
22324 }
22325 multi_buffer::Event::ExcerptsExpanded { ids } => {
22326 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22327 self.refresh_document_highlights(cx);
22328 for id in ids {
22329 self.fetched_tree_sitter_chunks.remove(id);
22330 }
22331 self.colorize_brackets(false, cx);
22332 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22333 }
22334 multi_buffer::Event::Reparsed(buffer_id) => {
22335 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22336 self.refresh_selected_text_highlights(true, window, cx);
22337 self.colorize_brackets(true, cx);
22338 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22339
22340 cx.emit(EditorEvent::Reparsed(*buffer_id));
22341 }
22342 multi_buffer::Event::DiffHunksToggled => {
22343 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22344 }
22345 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22346 if !is_fresh_language {
22347 self.registered_buffers.remove(&buffer_id);
22348 }
22349 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22350 cx.emit(EditorEvent::Reparsed(*buffer_id));
22351 cx.notify();
22352 }
22353 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22354 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22355 multi_buffer::Event::FileHandleChanged
22356 | multi_buffer::Event::Reloaded
22357 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22358 multi_buffer::Event::DiagnosticsUpdated => {
22359 self.update_diagnostics_state(window, cx);
22360 }
22361 _ => {}
22362 };
22363 }
22364
22365 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22366 if !self.diagnostics_enabled() {
22367 return;
22368 }
22369 self.refresh_active_diagnostics(cx);
22370 self.refresh_inline_diagnostics(true, window, cx);
22371 self.scrollbar_marker_state.dirty = true;
22372 cx.notify();
22373 }
22374
22375 pub fn start_temporary_diff_override(&mut self) {
22376 self.load_diff_task.take();
22377 self.temporary_diff_override = true;
22378 }
22379
22380 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22381 self.temporary_diff_override = false;
22382 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22383 self.buffer.update(cx, |buffer, cx| {
22384 buffer.set_all_diff_hunks_collapsed(cx);
22385 });
22386
22387 if let Some(project) = self.project.clone() {
22388 self.load_diff_task = Some(
22389 update_uncommitted_diff_for_buffer(
22390 cx.entity(),
22391 &project,
22392 self.buffer.read(cx).all_buffers(),
22393 self.buffer.clone(),
22394 cx,
22395 )
22396 .shared(),
22397 );
22398 }
22399 }
22400
22401 fn on_display_map_changed(
22402 &mut self,
22403 _: Entity<DisplayMap>,
22404 _: &mut Window,
22405 cx: &mut Context<Self>,
22406 ) {
22407 cx.notify();
22408 }
22409
22410 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22411 if !self.mode.is_full() {
22412 return None;
22413 }
22414
22415 let theme_settings = theme::ThemeSettings::get_global(cx);
22416 let theme = cx.theme();
22417 let accent_colors = theme.accents().clone();
22418
22419 let accent_overrides = theme_settings
22420 .theme_overrides
22421 .get(theme.name.as_ref())
22422 .map(|theme_style| &theme_style.accents)
22423 .into_iter()
22424 .flatten()
22425 .chain(
22426 theme_settings
22427 .experimental_theme_overrides
22428 .as_ref()
22429 .map(|overrides| &overrides.accents)
22430 .into_iter()
22431 .flatten(),
22432 )
22433 .flat_map(|accent| accent.0.clone())
22434 .collect();
22435
22436 Some(AccentData {
22437 colors: accent_colors,
22438 overrides: accent_overrides,
22439 })
22440 }
22441
22442 fn fetch_applicable_language_settings(
22443 &self,
22444 cx: &App,
22445 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22446 if !self.mode.is_full() {
22447 return HashMap::default();
22448 }
22449
22450 self.buffer().read(cx).all_buffers().into_iter().fold(
22451 HashMap::default(),
22452 |mut acc, buffer| {
22453 let buffer = buffer.read(cx);
22454 let language = buffer.language().map(|language| language.name());
22455 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22456 let file = buffer.file();
22457 v.insert(language_settings(language, file, cx).into_owned());
22458 }
22459 acc
22460 },
22461 )
22462 }
22463
22464 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22465 let new_language_settings = self.fetch_applicable_language_settings(cx);
22466 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22467 self.applicable_language_settings = new_language_settings;
22468
22469 let new_accents = self.fetch_accent_data(cx);
22470 let accents_changed = new_accents != self.accent_data;
22471 self.accent_data = new_accents;
22472
22473 if self.diagnostics_enabled() {
22474 let new_severity = EditorSettings::get_global(cx)
22475 .diagnostics_max_severity
22476 .unwrap_or(DiagnosticSeverity::Hint);
22477 self.set_max_diagnostics_severity(new_severity, cx);
22478 }
22479 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22480 self.update_edit_prediction_settings(cx);
22481 self.refresh_edit_prediction(true, false, window, cx);
22482 self.refresh_inline_values(cx);
22483 self.refresh_inlay_hints(
22484 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22485 self.selections.newest_anchor().head(),
22486 &self.buffer.read(cx).snapshot(cx),
22487 cx,
22488 )),
22489 cx,
22490 );
22491
22492 let old_cursor_shape = self.cursor_shape;
22493 let old_show_breadcrumbs = self.show_breadcrumbs;
22494
22495 {
22496 let editor_settings = EditorSettings::get_global(cx);
22497 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22498 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22499 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22500 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22501 }
22502
22503 if old_cursor_shape != self.cursor_shape {
22504 cx.emit(EditorEvent::CursorShapeChanged);
22505 }
22506
22507 if old_show_breadcrumbs != self.show_breadcrumbs {
22508 cx.emit(EditorEvent::BreadcrumbsChanged);
22509 }
22510
22511 let project_settings = ProjectSettings::get_global(cx);
22512 self.buffer_serialization = self
22513 .should_serialize_buffer()
22514 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22515
22516 if self.mode.is_full() {
22517 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22518 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22519 if self.show_inline_diagnostics != show_inline_diagnostics {
22520 self.show_inline_diagnostics = show_inline_diagnostics;
22521 self.refresh_inline_diagnostics(false, window, cx);
22522 }
22523
22524 if self.git_blame_inline_enabled != inline_blame_enabled {
22525 self.toggle_git_blame_inline_internal(false, window, cx);
22526 }
22527
22528 let minimap_settings = EditorSettings::get_global(cx).minimap;
22529 if self.minimap_visibility != MinimapVisibility::Disabled {
22530 if self.minimap_visibility.settings_visibility()
22531 != minimap_settings.minimap_enabled()
22532 {
22533 self.set_minimap_visibility(
22534 MinimapVisibility::for_mode(self.mode(), cx),
22535 window,
22536 cx,
22537 );
22538 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22539 minimap_entity.update(cx, |minimap_editor, cx| {
22540 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22541 })
22542 }
22543 }
22544
22545 if language_settings_changed || accents_changed {
22546 self.colorize_brackets(true, cx);
22547 }
22548
22549 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22550 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22551 }) {
22552 if !inlay_splice.is_empty() {
22553 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22554 }
22555 self.refresh_colors_for_visible_range(None, window, cx);
22556 }
22557 }
22558
22559 cx.notify();
22560 }
22561
22562 pub fn set_searchable(&mut self, searchable: bool) {
22563 self.searchable = searchable;
22564 }
22565
22566 pub fn searchable(&self) -> bool {
22567 self.searchable
22568 }
22569
22570 pub fn open_excerpts_in_split(
22571 &mut self,
22572 _: &OpenExcerptsSplit,
22573 window: &mut Window,
22574 cx: &mut Context<Self>,
22575 ) {
22576 self.open_excerpts_common(None, true, window, cx)
22577 }
22578
22579 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22580 self.open_excerpts_common(None, false, window, cx)
22581 }
22582
22583 fn open_excerpts_common(
22584 &mut self,
22585 jump_data: Option<JumpData>,
22586 split: bool,
22587 window: &mut Window,
22588 cx: &mut Context<Self>,
22589 ) {
22590 let Some(workspace) = self.workspace() else {
22591 cx.propagate();
22592 return;
22593 };
22594
22595 if self.buffer.read(cx).is_singleton() {
22596 cx.propagate();
22597 return;
22598 }
22599
22600 let mut new_selections_by_buffer = HashMap::default();
22601 match &jump_data {
22602 Some(JumpData::MultiBufferPoint {
22603 excerpt_id,
22604 position,
22605 anchor,
22606 line_offset_from_top,
22607 }) => {
22608 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22609 if let Some(buffer) = multi_buffer_snapshot
22610 .buffer_id_for_excerpt(*excerpt_id)
22611 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22612 {
22613 let buffer_snapshot = buffer.read(cx).snapshot();
22614 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22615 language::ToPoint::to_point(anchor, &buffer_snapshot)
22616 } else {
22617 buffer_snapshot.clip_point(*position, Bias::Left)
22618 };
22619 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22620 new_selections_by_buffer.insert(
22621 buffer,
22622 (
22623 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22624 Some(*line_offset_from_top),
22625 ),
22626 );
22627 }
22628 }
22629 Some(JumpData::MultiBufferRow {
22630 row,
22631 line_offset_from_top,
22632 }) => {
22633 let point = MultiBufferPoint::new(row.0, 0);
22634 if let Some((buffer, buffer_point, _)) =
22635 self.buffer.read(cx).point_to_buffer_point(point, cx)
22636 {
22637 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22638 new_selections_by_buffer
22639 .entry(buffer)
22640 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22641 .0
22642 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22643 }
22644 }
22645 None => {
22646 let selections = self
22647 .selections
22648 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22649 let multi_buffer = self.buffer.read(cx);
22650 for selection in selections {
22651 for (snapshot, range, _, anchor) in multi_buffer
22652 .snapshot(cx)
22653 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22654 {
22655 if let Some(anchor) = anchor {
22656 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22657 else {
22658 continue;
22659 };
22660 let offset = text::ToOffset::to_offset(
22661 &anchor.text_anchor,
22662 &buffer_handle.read(cx).snapshot(),
22663 );
22664 let range = BufferOffset(offset)..BufferOffset(offset);
22665 new_selections_by_buffer
22666 .entry(buffer_handle)
22667 .or_insert((Vec::new(), None))
22668 .0
22669 .push(range)
22670 } else {
22671 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22672 else {
22673 continue;
22674 };
22675 new_selections_by_buffer
22676 .entry(buffer_handle)
22677 .or_insert((Vec::new(), None))
22678 .0
22679 .push(range)
22680 }
22681 }
22682 }
22683 }
22684 }
22685
22686 new_selections_by_buffer
22687 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
22688
22689 if new_selections_by_buffer.is_empty() {
22690 return;
22691 }
22692
22693 // We defer the pane interaction because we ourselves are a workspace item
22694 // and activating a new item causes the pane to call a method on us reentrantly,
22695 // which panics if we're on the stack.
22696 window.defer(cx, move |window, cx| {
22697 workspace.update(cx, |workspace, cx| {
22698 let pane = if split {
22699 workspace.adjacent_pane(window, cx)
22700 } else {
22701 workspace.active_pane().clone()
22702 };
22703
22704 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22705 let buffer_read = buffer.read(cx);
22706 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22707 (true, project::File::from_dyn(Some(file)).is_some())
22708 } else {
22709 (false, false)
22710 };
22711
22712 // If project file is none workspace.open_project_item will fail to open the excerpt
22713 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22714 // so we check if there's a tab match in that case first
22715 let editor = (!has_file || !is_project_file)
22716 .then(|| {
22717 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22718 // so `workspace.open_project_item` will never find them, always opening a new editor.
22719 // Instead, we try to activate the existing editor in the pane first.
22720 let (editor, pane_item_index, pane_item_id) =
22721 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22722 let editor = item.downcast::<Editor>()?;
22723 let singleton_buffer =
22724 editor.read(cx).buffer().read(cx).as_singleton()?;
22725 if singleton_buffer == buffer {
22726 Some((editor, i, item.item_id()))
22727 } else {
22728 None
22729 }
22730 })?;
22731 pane.update(cx, |pane, cx| {
22732 pane.activate_item(pane_item_index, true, true, window, cx);
22733 if !PreviewTabsSettings::get_global(cx)
22734 .enable_preview_from_multibuffer
22735 {
22736 pane.unpreview_item_if_preview(pane_item_id);
22737 }
22738 });
22739 Some(editor)
22740 })
22741 .flatten()
22742 .unwrap_or_else(|| {
22743 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22744 .enable_keep_preview_on_code_navigation;
22745 let allow_new_preview =
22746 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22747 workspace.open_project_item::<Self>(
22748 pane.clone(),
22749 buffer,
22750 true,
22751 true,
22752 keep_old_preview,
22753 allow_new_preview,
22754 window,
22755 cx,
22756 )
22757 });
22758
22759 editor.update(cx, |editor, cx| {
22760 if has_file && !is_project_file {
22761 editor.set_read_only(true);
22762 }
22763 let autoscroll = match scroll_offset {
22764 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22765 None => Autoscroll::newest(),
22766 };
22767 let nav_history = editor.nav_history.take();
22768 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22769 let Some((&excerpt_id, _, buffer_snapshot)) =
22770 multibuffer_snapshot.as_singleton()
22771 else {
22772 return;
22773 };
22774 editor.change_selections(
22775 SelectionEffects::scroll(autoscroll),
22776 window,
22777 cx,
22778 |s| {
22779 s.select_ranges(ranges.into_iter().map(|range| {
22780 let range = buffer_snapshot.anchor_before(range.start)
22781 ..buffer_snapshot.anchor_after(range.end);
22782 multibuffer_snapshot
22783 .anchor_range_in_excerpt(excerpt_id, range)
22784 .unwrap()
22785 }));
22786 },
22787 );
22788 editor.nav_history = nav_history;
22789 });
22790 }
22791 })
22792 });
22793 }
22794
22795 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22796 let snapshot = self.buffer.read(cx).read(cx);
22797 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22798 Some(
22799 ranges
22800 .iter()
22801 .map(move |range| {
22802 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22803 })
22804 .collect(),
22805 )
22806 }
22807
22808 fn selection_replacement_ranges(
22809 &self,
22810 range: Range<MultiBufferOffsetUtf16>,
22811 cx: &mut App,
22812 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22813 let selections = self
22814 .selections
22815 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22816 let newest_selection = selections
22817 .iter()
22818 .max_by_key(|selection| selection.id)
22819 .unwrap();
22820 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22821 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22822 let snapshot = self.buffer.read(cx).read(cx);
22823 selections
22824 .into_iter()
22825 .map(|mut selection| {
22826 selection.start.0.0 =
22827 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22828 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22829 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22830 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22831 })
22832 .collect()
22833 }
22834
22835 fn report_editor_event(
22836 &self,
22837 reported_event: ReportEditorEvent,
22838 file_extension: Option<String>,
22839 cx: &App,
22840 ) {
22841 if cfg!(any(test, feature = "test-support")) {
22842 return;
22843 }
22844
22845 let Some(project) = &self.project else { return };
22846
22847 // If None, we are in a file without an extension
22848 let file = self
22849 .buffer
22850 .read(cx)
22851 .as_singleton()
22852 .and_then(|b| b.read(cx).file());
22853 let file_extension = file_extension.or(file
22854 .as_ref()
22855 .and_then(|file| Path::new(file.file_name(cx)).extension())
22856 .and_then(|e| e.to_str())
22857 .map(|a| a.to_string()));
22858
22859 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22860 .map(|vim_mode| vim_mode.0)
22861 .unwrap_or(false);
22862
22863 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22864 let copilot_enabled = edit_predictions_provider
22865 == language::language_settings::EditPredictionProvider::Copilot;
22866 let copilot_enabled_for_language = self
22867 .buffer
22868 .read(cx)
22869 .language_settings(cx)
22870 .show_edit_predictions;
22871
22872 let project = project.read(cx);
22873 let event_type = reported_event.event_type();
22874
22875 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22876 telemetry::event!(
22877 event_type,
22878 type = if auto_saved {"autosave"} else {"manual"},
22879 file_extension,
22880 vim_mode,
22881 copilot_enabled,
22882 copilot_enabled_for_language,
22883 edit_predictions_provider,
22884 is_via_ssh = project.is_via_remote_server(),
22885 );
22886 } else {
22887 telemetry::event!(
22888 event_type,
22889 file_extension,
22890 vim_mode,
22891 copilot_enabled,
22892 copilot_enabled_for_language,
22893 edit_predictions_provider,
22894 is_via_ssh = project.is_via_remote_server(),
22895 );
22896 };
22897 }
22898
22899 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22900 /// with each line being an array of {text, highlight} objects.
22901 fn copy_highlight_json(
22902 &mut self,
22903 _: &CopyHighlightJson,
22904 window: &mut Window,
22905 cx: &mut Context<Self>,
22906 ) {
22907 #[derive(Serialize)]
22908 struct Chunk<'a> {
22909 text: String,
22910 highlight: Option<&'a str>,
22911 }
22912
22913 let snapshot = self.buffer.read(cx).snapshot(cx);
22914 let range = self
22915 .selected_text_range(false, window, cx)
22916 .and_then(|selection| {
22917 if selection.range.is_empty() {
22918 None
22919 } else {
22920 Some(
22921 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22922 selection.range.start,
22923 )))
22924 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22925 selection.range.end,
22926 ))),
22927 )
22928 }
22929 })
22930 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22931
22932 let chunks = snapshot.chunks(range, true);
22933 let mut lines = Vec::new();
22934 let mut line: VecDeque<Chunk> = VecDeque::new();
22935
22936 let Some(style) = self.style.as_ref() else {
22937 return;
22938 };
22939
22940 for chunk in chunks {
22941 let highlight = chunk
22942 .syntax_highlight_id
22943 .and_then(|id| id.name(&style.syntax));
22944 let mut chunk_lines = chunk.text.split('\n').peekable();
22945 while let Some(text) = chunk_lines.next() {
22946 let mut merged_with_last_token = false;
22947 if let Some(last_token) = line.back_mut()
22948 && last_token.highlight == highlight
22949 {
22950 last_token.text.push_str(text);
22951 merged_with_last_token = true;
22952 }
22953
22954 if !merged_with_last_token {
22955 line.push_back(Chunk {
22956 text: text.into(),
22957 highlight,
22958 });
22959 }
22960
22961 if chunk_lines.peek().is_some() {
22962 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22963 line.pop_front();
22964 }
22965 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22966 line.pop_back();
22967 }
22968
22969 lines.push(mem::take(&mut line));
22970 }
22971 }
22972 }
22973
22974 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22975 return;
22976 };
22977 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22978 }
22979
22980 pub fn open_context_menu(
22981 &mut self,
22982 _: &OpenContextMenu,
22983 window: &mut Window,
22984 cx: &mut Context<Self>,
22985 ) {
22986 self.request_autoscroll(Autoscroll::newest(), cx);
22987 let position = self
22988 .selections
22989 .newest_display(&self.display_snapshot(cx))
22990 .start;
22991 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22992 }
22993
22994 pub fn replay_insert_event(
22995 &mut self,
22996 text: &str,
22997 relative_utf16_range: Option<Range<isize>>,
22998 window: &mut Window,
22999 cx: &mut Context<Self>,
23000 ) {
23001 if !self.input_enabled {
23002 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23003 return;
23004 }
23005 if let Some(relative_utf16_range) = relative_utf16_range {
23006 let selections = self
23007 .selections
23008 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
23009 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23010 let new_ranges = selections.into_iter().map(|range| {
23011 let start = MultiBufferOffsetUtf16(OffsetUtf16(
23012 range
23013 .head()
23014 .0
23015 .0
23016 .saturating_add_signed(relative_utf16_range.start),
23017 ));
23018 let end = MultiBufferOffsetUtf16(OffsetUtf16(
23019 range
23020 .head()
23021 .0
23022 .0
23023 .saturating_add_signed(relative_utf16_range.end),
23024 ));
23025 start..end
23026 });
23027 s.select_ranges(new_ranges);
23028 });
23029 }
23030
23031 self.handle_input(text, window, cx);
23032 }
23033
23034 pub fn is_focused(&self, window: &Window) -> bool {
23035 self.focus_handle.is_focused(window)
23036 }
23037
23038 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23039 cx.emit(EditorEvent::Focused);
23040
23041 if let Some(descendant) = self
23042 .last_focused_descendant
23043 .take()
23044 .and_then(|descendant| descendant.upgrade())
23045 {
23046 window.focus(&descendant, cx);
23047 } else {
23048 if let Some(blame) = self.blame.as_ref() {
23049 blame.update(cx, GitBlame::focus)
23050 }
23051
23052 self.blink_manager.update(cx, BlinkManager::enable);
23053 self.show_cursor_names(window, cx);
23054 self.buffer.update(cx, |buffer, cx| {
23055 buffer.finalize_last_transaction(cx);
23056 if self.leader_id.is_none() {
23057 buffer.set_active_selections(
23058 &self.selections.disjoint_anchors_arc(),
23059 self.selections.line_mode(),
23060 self.cursor_shape,
23061 cx,
23062 );
23063 }
23064 });
23065
23066 if let Some(position_map) = self.last_position_map.clone() {
23067 EditorElement::mouse_moved(
23068 self,
23069 &MouseMoveEvent {
23070 position: window.mouse_position(),
23071 pressed_button: None,
23072 modifiers: window.modifiers(),
23073 },
23074 &position_map,
23075 window,
23076 cx,
23077 );
23078 }
23079 }
23080 }
23081
23082 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23083 cx.emit(EditorEvent::FocusedIn)
23084 }
23085
23086 fn handle_focus_out(
23087 &mut self,
23088 event: FocusOutEvent,
23089 _window: &mut Window,
23090 cx: &mut Context<Self>,
23091 ) {
23092 if event.blurred != self.focus_handle {
23093 self.last_focused_descendant = Some(event.blurred);
23094 }
23095 self.selection_drag_state = SelectionDragState::None;
23096 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
23097 }
23098
23099 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23100 self.blink_manager.update(cx, BlinkManager::disable);
23101 self.buffer
23102 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
23103
23104 if let Some(blame) = self.blame.as_ref() {
23105 blame.update(cx, GitBlame::blur)
23106 }
23107 if !self.hover_state.focused(window, cx) {
23108 hide_hover(self, cx);
23109 }
23110 if !self
23111 .context_menu
23112 .borrow()
23113 .as_ref()
23114 .is_some_and(|context_menu| context_menu.focused(window, cx))
23115 {
23116 self.hide_context_menu(window, cx);
23117 }
23118 self.take_active_edit_prediction(cx);
23119 cx.emit(EditorEvent::Blurred);
23120 cx.notify();
23121 }
23122
23123 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23124 let mut pending: String = window
23125 .pending_input_keystrokes()
23126 .into_iter()
23127 .flatten()
23128 .filter_map(|keystroke| keystroke.key_char.clone())
23129 .collect();
23130
23131 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
23132 pending = "".to_string();
23133 }
23134
23135 let existing_pending = self
23136 .text_highlights::<PendingInput>(cx)
23137 .map(|(_, ranges)| ranges.to_vec());
23138 if existing_pending.is_none() && pending.is_empty() {
23139 return;
23140 }
23141 let transaction =
23142 self.transact(window, cx, |this, window, cx| {
23143 let selections = this
23144 .selections
23145 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
23146 let edits = selections
23147 .iter()
23148 .map(|selection| (selection.end..selection.end, pending.clone()));
23149 this.edit(edits, cx);
23150 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23151 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
23152 sel.start + ix * pending.len()..sel.end + ix * pending.len()
23153 }));
23154 });
23155 if let Some(existing_ranges) = existing_pending {
23156 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
23157 this.edit(edits, cx);
23158 }
23159 });
23160
23161 let snapshot = self.snapshot(window, cx);
23162 let ranges = self
23163 .selections
23164 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
23165 .into_iter()
23166 .map(|selection| {
23167 snapshot.buffer_snapshot().anchor_after(selection.end)
23168 ..snapshot
23169 .buffer_snapshot()
23170 .anchor_before(selection.end + pending.len())
23171 })
23172 .collect();
23173
23174 if pending.is_empty() {
23175 self.clear_highlights::<PendingInput>(cx);
23176 } else {
23177 self.highlight_text::<PendingInput>(
23178 ranges,
23179 HighlightStyle {
23180 underline: Some(UnderlineStyle {
23181 thickness: px(1.),
23182 color: None,
23183 wavy: false,
23184 }),
23185 ..Default::default()
23186 },
23187 cx,
23188 );
23189 }
23190
23191 self.ime_transaction = self.ime_transaction.or(transaction);
23192 if let Some(transaction) = self.ime_transaction {
23193 self.buffer.update(cx, |buffer, cx| {
23194 buffer.group_until_transaction(transaction, cx);
23195 });
23196 }
23197
23198 if self.text_highlights::<PendingInput>(cx).is_none() {
23199 self.ime_transaction.take();
23200 }
23201 }
23202
23203 pub fn register_action_renderer(
23204 &mut self,
23205 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
23206 ) -> Subscription {
23207 let id = self.next_editor_action_id.post_inc();
23208 self.editor_actions
23209 .borrow_mut()
23210 .insert(id, Box::new(listener));
23211
23212 let editor_actions = self.editor_actions.clone();
23213 Subscription::new(move || {
23214 editor_actions.borrow_mut().remove(&id);
23215 })
23216 }
23217
23218 pub fn register_action<A: Action>(
23219 &mut self,
23220 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
23221 ) -> Subscription {
23222 let id = self.next_editor_action_id.post_inc();
23223 let listener = Arc::new(listener);
23224 self.editor_actions.borrow_mut().insert(
23225 id,
23226 Box::new(move |_, window, _| {
23227 let listener = listener.clone();
23228 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
23229 let action = action.downcast_ref().unwrap();
23230 if phase == DispatchPhase::Bubble {
23231 listener(action, window, cx)
23232 }
23233 })
23234 }),
23235 );
23236
23237 let editor_actions = self.editor_actions.clone();
23238 Subscription::new(move || {
23239 editor_actions.borrow_mut().remove(&id);
23240 })
23241 }
23242
23243 pub fn file_header_size(&self) -> u32 {
23244 FILE_HEADER_HEIGHT
23245 }
23246
23247 pub fn restore(
23248 &mut self,
23249 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
23250 window: &mut Window,
23251 cx: &mut Context<Self>,
23252 ) {
23253 self.buffer().update(cx, |multi_buffer, cx| {
23254 for (buffer_id, changes) in revert_changes {
23255 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
23256 buffer.update(cx, |buffer, cx| {
23257 buffer.edit(
23258 changes
23259 .into_iter()
23260 .map(|(range, text)| (range, text.to_string())),
23261 None,
23262 cx,
23263 );
23264 });
23265 }
23266 }
23267 });
23268 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23269 selections.refresh()
23270 });
23271 }
23272
23273 pub fn to_pixel_point(
23274 &mut self,
23275 source: multi_buffer::Anchor,
23276 editor_snapshot: &EditorSnapshot,
23277 window: &mut Window,
23278 cx: &App,
23279 ) -> Option<gpui::Point<Pixels>> {
23280 let source_point = source.to_display_point(editor_snapshot);
23281 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23282 }
23283
23284 pub fn display_to_pixel_point(
23285 &mut self,
23286 source: DisplayPoint,
23287 editor_snapshot: &EditorSnapshot,
23288 window: &mut Window,
23289 cx: &App,
23290 ) -> Option<gpui::Point<Pixels>> {
23291 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23292 let text_layout_details = self.text_layout_details(window);
23293 let scroll_top = text_layout_details
23294 .scroll_anchor
23295 .scroll_position(editor_snapshot)
23296 .y;
23297
23298 if source.row().as_f64() < scroll_top.floor() {
23299 return None;
23300 }
23301 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23302 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23303 Some(gpui::Point::new(source_x, source_y))
23304 }
23305
23306 pub fn has_visible_completions_menu(&self) -> bool {
23307 !self.edit_prediction_preview_is_active()
23308 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23309 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23310 })
23311 }
23312
23313 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23314 if self.mode.is_minimap() {
23315 return;
23316 }
23317 self.addons
23318 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23319 }
23320
23321 pub fn unregister_addon<T: Addon>(&mut self) {
23322 self.addons.remove(&std::any::TypeId::of::<T>());
23323 }
23324
23325 pub fn addon<T: Addon>(&self) -> Option<&T> {
23326 let type_id = std::any::TypeId::of::<T>();
23327 self.addons
23328 .get(&type_id)
23329 .and_then(|item| item.to_any().downcast_ref::<T>())
23330 }
23331
23332 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23333 let type_id = std::any::TypeId::of::<T>();
23334 self.addons
23335 .get_mut(&type_id)
23336 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23337 }
23338
23339 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23340 let text_layout_details = self.text_layout_details(window);
23341 let style = &text_layout_details.editor_style;
23342 let font_id = window.text_system().resolve_font(&style.text.font());
23343 let font_size = style.text.font_size.to_pixels(window.rem_size());
23344 let line_height = style.text.line_height_in_pixels(window.rem_size());
23345 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23346 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23347
23348 CharacterDimensions {
23349 em_width,
23350 em_advance,
23351 line_height,
23352 }
23353 }
23354
23355 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23356 self.load_diff_task.clone()
23357 }
23358
23359 fn read_metadata_from_db(
23360 &mut self,
23361 item_id: u64,
23362 workspace_id: WorkspaceId,
23363 window: &mut Window,
23364 cx: &mut Context<Editor>,
23365 ) {
23366 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23367 && !self.mode.is_minimap()
23368 && WorkspaceSettings::get(None, cx).restore_on_startup
23369 != RestoreOnStartupBehavior::EmptyTab
23370 {
23371 let buffer_snapshot = OnceCell::new();
23372
23373 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23374 && !folds.is_empty()
23375 {
23376 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23377 let snapshot_len = snapshot.len().0;
23378
23379 // Helper: search for fingerprint in buffer, return offset if found
23380 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
23381 // Ensure we start at a character boundary (defensive)
23382 let search_start = snapshot
23383 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
23384 .0;
23385 let search_end = snapshot_len.saturating_sub(fingerprint.len());
23386
23387 let mut byte_offset = search_start;
23388 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
23389 if byte_offset > search_end {
23390 break;
23391 }
23392 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
23393 return Some(byte_offset);
23394 }
23395 byte_offset += ch.len_utf8();
23396 }
23397 None
23398 };
23399
23400 // Track search position to handle duplicate fingerprints correctly.
23401 // Folds are stored in document order, so we advance after each match.
23402 let mut search_start = 0usize;
23403
23404 let valid_folds: Vec<_> = folds
23405 .into_iter()
23406 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
23407 // Skip folds without fingerprints (old data before migration)
23408 let sfp = start_fp?;
23409 let efp = end_fp?;
23410 let efp_len = efp.len();
23411
23412 // Fast path: check if fingerprints match at stored offsets
23413 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
23414 let start_matches = stored_start < snapshot_len
23415 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
23416 let efp_check_pos = stored_end.saturating_sub(efp_len);
23417 let end_matches = efp_check_pos >= stored_start
23418 && stored_end <= snapshot_len
23419 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
23420
23421 let (new_start, new_end) = if start_matches && end_matches {
23422 // Offsets unchanged, use stored values
23423 (stored_start, stored_end)
23424 } else if sfp == efp {
23425 // Short fold: identical fingerprints can only match once per search
23426 // Use stored fold length to compute new_end
23427 let new_start = find_fingerprint(&sfp, search_start)?;
23428 let fold_len = stored_end - stored_start;
23429 let new_end = new_start + fold_len;
23430 (new_start, new_end)
23431 } else {
23432 // Slow path: search for fingerprints in buffer
23433 let new_start = find_fingerprint(&sfp, search_start)?;
23434 // Search for end_fp after start, then add efp_len to get actual fold end
23435 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
23436 let new_end = efp_pos + efp_len;
23437 (new_start, new_end)
23438 };
23439
23440 // Advance search position for next fold
23441 search_start = new_end;
23442
23443 // Validate fold makes sense (end must be after start)
23444 if new_end <= new_start {
23445 return None;
23446 }
23447
23448 Some(
23449 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
23450 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
23451 )
23452 })
23453 .collect();
23454
23455 if !valid_folds.is_empty() {
23456 self.fold_ranges(valid_folds, false, window, cx);
23457
23458 // Migrate folds to current entity_id before workspace cleanup runs.
23459 // Entity IDs change between sessions, but workspace cleanup deletes
23460 // old editor rows (cascading to folds) based on current entity IDs.
23461 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
23462 if new_editor_id != item_id {
23463 cx.spawn(async move |_, _| {
23464 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
23465 .await
23466 .log_err();
23467 })
23468 .detach();
23469 }
23470 }
23471 }
23472
23473 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23474 && !selections.is_empty()
23475 {
23476 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23477 // skip adding the initial selection to selection history
23478 self.selection_history.mode = SelectionHistoryMode::Skipping;
23479 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23480 s.select_ranges(selections.into_iter().map(|(start, end)| {
23481 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23482 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23483 }));
23484 });
23485 self.selection_history.mode = SelectionHistoryMode::Normal;
23486 };
23487 }
23488
23489 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23490 }
23491
23492 fn update_lsp_data(
23493 &mut self,
23494 for_buffer: Option<BufferId>,
23495 window: &mut Window,
23496 cx: &mut Context<'_, Self>,
23497 ) {
23498 self.pull_diagnostics(for_buffer, window, cx);
23499 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23500 }
23501
23502 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23503 if self.ignore_lsp_data() {
23504 return;
23505 }
23506 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23507 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23508 }
23509 }
23510
23511 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23512 if self.ignore_lsp_data() {
23513 return;
23514 }
23515
23516 if !self.registered_buffers.contains_key(&buffer_id)
23517 && let Some(project) = self.project.as_ref()
23518 {
23519 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23520 project.update(cx, |project, cx| {
23521 self.registered_buffers.insert(
23522 buffer_id,
23523 project.register_buffer_with_language_servers(&buffer, cx),
23524 );
23525 });
23526 } else {
23527 self.registered_buffers.remove(&buffer_id);
23528 }
23529 }
23530 }
23531
23532 fn ignore_lsp_data(&self) -> bool {
23533 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23534 // skip any LSP updates for it.
23535 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23536 }
23537
23538 fn create_style(&self, cx: &App) -> EditorStyle {
23539 let settings = ThemeSettings::get_global(cx);
23540
23541 let mut text_style = match self.mode {
23542 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23543 color: cx.theme().colors().editor_foreground,
23544 font_family: settings.ui_font.family.clone(),
23545 font_features: settings.ui_font.features.clone(),
23546 font_fallbacks: settings.ui_font.fallbacks.clone(),
23547 font_size: rems(0.875).into(),
23548 font_weight: settings.ui_font.weight,
23549 line_height: relative(settings.buffer_line_height.value()),
23550 ..Default::default()
23551 },
23552 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23553 color: cx.theme().colors().editor_foreground,
23554 font_family: settings.buffer_font.family.clone(),
23555 font_features: settings.buffer_font.features.clone(),
23556 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23557 font_size: settings.buffer_font_size(cx).into(),
23558 font_weight: settings.buffer_font.weight,
23559 line_height: relative(settings.buffer_line_height.value()),
23560 ..Default::default()
23561 },
23562 };
23563 if let Some(text_style_refinement) = &self.text_style_refinement {
23564 text_style.refine(text_style_refinement)
23565 }
23566
23567 let background = match self.mode {
23568 EditorMode::SingleLine => cx.theme().system().transparent,
23569 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23570 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23571 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23572 };
23573
23574 EditorStyle {
23575 background,
23576 border: cx.theme().colors().border,
23577 local_player: cx.theme().players().local(),
23578 text: text_style,
23579 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23580 syntax: cx.theme().syntax().clone(),
23581 status: cx.theme().status().clone(),
23582 inlay_hints_style: make_inlay_hints_style(cx),
23583 edit_prediction_styles: make_suggestion_styles(cx),
23584 unnecessary_code_fade: settings.unnecessary_code_fade,
23585 show_underlines: self.diagnostics_enabled(),
23586 }
23587 }
23588 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
23589 let cursor = self.selections.newest_anchor().head();
23590 let multibuffer = self.buffer().read(cx);
23591 let is_singleton = multibuffer.is_singleton();
23592 let (buffer_id, symbols) = multibuffer
23593 .read(cx)
23594 .symbols_containing(cursor, Some(variant.syntax()))?;
23595 let buffer = multibuffer.buffer(buffer_id)?;
23596
23597 let buffer = buffer.read(cx);
23598 let settings = ThemeSettings::get_global(cx);
23599 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
23600 let mut breadcrumbs = if is_singleton {
23601 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
23602 buffer
23603 .snapshot()
23604 .resolve_file_path(
23605 self.project
23606 .as_ref()
23607 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
23608 .unwrap_or_default(),
23609 cx,
23610 )
23611 .unwrap_or_else(|| {
23612 if multibuffer.is_singleton() {
23613 multibuffer.title(cx).to_string()
23614 } else {
23615 "untitled".to_string()
23616 }
23617 })
23618 });
23619 vec![BreadcrumbText {
23620 text,
23621 highlights: None,
23622 font: Some(settings.buffer_font.clone()),
23623 }]
23624 } else {
23625 vec![]
23626 };
23627
23628 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
23629 text: symbol.text,
23630 highlights: Some(symbol.highlight_ranges),
23631 font: Some(settings.buffer_font.clone()),
23632 }));
23633 Some(breadcrumbs)
23634 }
23635}
23636
23637fn edit_for_markdown_paste<'a>(
23638 buffer: &MultiBufferSnapshot,
23639 range: Range<MultiBufferOffset>,
23640 to_insert: &'a str,
23641 url: Option<url::Url>,
23642) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23643 if url.is_none() {
23644 return (range, Cow::Borrowed(to_insert));
23645 };
23646
23647 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23648
23649 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23650 Cow::Borrowed(to_insert)
23651 } else {
23652 Cow::Owned(format!("[{old_text}]({to_insert})"))
23653 };
23654 (range, new_text)
23655}
23656
23657fn process_completion_for_edit(
23658 completion: &Completion,
23659 intent: CompletionIntent,
23660 buffer: &Entity<Buffer>,
23661 cursor_position: &text::Anchor,
23662 cx: &mut Context<Editor>,
23663) -> CompletionEdit {
23664 let buffer = buffer.read(cx);
23665 let buffer_snapshot = buffer.snapshot();
23666 let (snippet, new_text) = if completion.is_snippet() {
23667 let mut snippet_source = completion.new_text.clone();
23668 // Workaround for typescript language server issues so that methods don't expand within
23669 // strings and functions with type expressions. The previous point is used because the query
23670 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23671 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23672 let previous_point = if previous_point.column > 0 {
23673 cursor_position.to_previous_offset(&buffer_snapshot)
23674 } else {
23675 cursor_position.to_offset(&buffer_snapshot)
23676 };
23677 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23678 && scope.prefers_label_for_snippet_in_completion()
23679 && let Some(label) = completion.label()
23680 && matches!(
23681 completion.kind(),
23682 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23683 )
23684 {
23685 snippet_source = label;
23686 }
23687 match Snippet::parse(&snippet_source).log_err() {
23688 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23689 None => (None, completion.new_text.clone()),
23690 }
23691 } else {
23692 (None, completion.new_text.clone())
23693 };
23694
23695 let mut range_to_replace = {
23696 let replace_range = &completion.replace_range;
23697 if let CompletionSource::Lsp {
23698 insert_range: Some(insert_range),
23699 ..
23700 } = &completion.source
23701 {
23702 debug_assert_eq!(
23703 insert_range.start, replace_range.start,
23704 "insert_range and replace_range should start at the same position"
23705 );
23706 debug_assert!(
23707 insert_range
23708 .start
23709 .cmp(cursor_position, &buffer_snapshot)
23710 .is_le(),
23711 "insert_range should start before or at cursor position"
23712 );
23713 debug_assert!(
23714 replace_range
23715 .start
23716 .cmp(cursor_position, &buffer_snapshot)
23717 .is_le(),
23718 "replace_range should start before or at cursor position"
23719 );
23720
23721 let should_replace = match intent {
23722 CompletionIntent::CompleteWithInsert => false,
23723 CompletionIntent::CompleteWithReplace => true,
23724 CompletionIntent::Complete | CompletionIntent::Compose => {
23725 let insert_mode =
23726 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23727 .completions
23728 .lsp_insert_mode;
23729 match insert_mode {
23730 LspInsertMode::Insert => false,
23731 LspInsertMode::Replace => true,
23732 LspInsertMode::ReplaceSubsequence => {
23733 let mut text_to_replace = buffer.chars_for_range(
23734 buffer.anchor_before(replace_range.start)
23735 ..buffer.anchor_after(replace_range.end),
23736 );
23737 let mut current_needle = text_to_replace.next();
23738 for haystack_ch in completion.label.text.chars() {
23739 if let Some(needle_ch) = current_needle
23740 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23741 {
23742 current_needle = text_to_replace.next();
23743 }
23744 }
23745 current_needle.is_none()
23746 }
23747 LspInsertMode::ReplaceSuffix => {
23748 if replace_range
23749 .end
23750 .cmp(cursor_position, &buffer_snapshot)
23751 .is_gt()
23752 {
23753 let range_after_cursor = *cursor_position..replace_range.end;
23754 let text_after_cursor = buffer
23755 .text_for_range(
23756 buffer.anchor_before(range_after_cursor.start)
23757 ..buffer.anchor_after(range_after_cursor.end),
23758 )
23759 .collect::<String>()
23760 .to_ascii_lowercase();
23761 completion
23762 .label
23763 .text
23764 .to_ascii_lowercase()
23765 .ends_with(&text_after_cursor)
23766 } else {
23767 true
23768 }
23769 }
23770 }
23771 }
23772 };
23773
23774 if should_replace {
23775 replace_range.clone()
23776 } else {
23777 insert_range.clone()
23778 }
23779 } else {
23780 replace_range.clone()
23781 }
23782 };
23783
23784 if range_to_replace
23785 .end
23786 .cmp(cursor_position, &buffer_snapshot)
23787 .is_lt()
23788 {
23789 range_to_replace.end = *cursor_position;
23790 }
23791
23792 let replace_range = range_to_replace.to_offset(buffer);
23793 CompletionEdit {
23794 new_text,
23795 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23796 snippet,
23797 }
23798}
23799
23800struct CompletionEdit {
23801 new_text: String,
23802 replace_range: Range<BufferOffset>,
23803 snippet: Option<Snippet>,
23804}
23805
23806fn comment_delimiter_for_newline(
23807 start_point: &Point,
23808 buffer: &MultiBufferSnapshot,
23809 language: &LanguageScope,
23810) -> Option<Arc<str>> {
23811 let delimiters = language.line_comment_prefixes();
23812 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23813 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23814
23815 let num_of_whitespaces = snapshot
23816 .chars_for_range(range.clone())
23817 .take_while(|c| c.is_whitespace())
23818 .count();
23819 let comment_candidate = snapshot
23820 .chars_for_range(range.clone())
23821 .skip(num_of_whitespaces)
23822 .take(max_len_of_delimiter)
23823 .collect::<String>();
23824 let (delimiter, trimmed_len) = delimiters
23825 .iter()
23826 .filter_map(|delimiter| {
23827 let prefix = delimiter.trim_end();
23828 if comment_candidate.starts_with(prefix) {
23829 Some((delimiter, prefix.len()))
23830 } else {
23831 None
23832 }
23833 })
23834 .max_by_key(|(_, len)| *len)?;
23835
23836 if let Some(BlockCommentConfig {
23837 start: block_start, ..
23838 }) = language.block_comment()
23839 {
23840 let block_start_trimmed = block_start.trim_end();
23841 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23842 let line_content = snapshot
23843 .chars_for_range(range)
23844 .skip(num_of_whitespaces)
23845 .take(block_start_trimmed.len())
23846 .collect::<String>();
23847
23848 if line_content.starts_with(block_start_trimmed) {
23849 return None;
23850 }
23851 }
23852 }
23853
23854 let cursor_is_placed_after_comment_marker =
23855 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23856 if cursor_is_placed_after_comment_marker {
23857 Some(delimiter.clone())
23858 } else {
23859 None
23860 }
23861}
23862
23863fn documentation_delimiter_for_newline(
23864 start_point: &Point,
23865 buffer: &MultiBufferSnapshot,
23866 language: &LanguageScope,
23867 newline_config: &mut NewlineConfig,
23868) -> Option<Arc<str>> {
23869 let BlockCommentConfig {
23870 start: start_tag,
23871 end: end_tag,
23872 prefix: delimiter,
23873 tab_size: len,
23874 } = language.documentation_comment()?;
23875 let is_within_block_comment = buffer
23876 .language_scope_at(*start_point)
23877 .is_some_and(|scope| scope.override_name() == Some("comment"));
23878 if !is_within_block_comment {
23879 return None;
23880 }
23881
23882 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23883
23884 let num_of_whitespaces = snapshot
23885 .chars_for_range(range.clone())
23886 .take_while(|c| c.is_whitespace())
23887 .count();
23888
23889 // 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.
23890 let column = start_point.column;
23891 let cursor_is_after_start_tag = {
23892 let start_tag_len = start_tag.len();
23893 let start_tag_line = snapshot
23894 .chars_for_range(range.clone())
23895 .skip(num_of_whitespaces)
23896 .take(start_tag_len)
23897 .collect::<String>();
23898 if start_tag_line.starts_with(start_tag.as_ref()) {
23899 num_of_whitespaces + start_tag_len <= column as usize
23900 } else {
23901 false
23902 }
23903 };
23904
23905 let cursor_is_after_delimiter = {
23906 let delimiter_trim = delimiter.trim_end();
23907 let delimiter_line = snapshot
23908 .chars_for_range(range.clone())
23909 .skip(num_of_whitespaces)
23910 .take(delimiter_trim.len())
23911 .collect::<String>();
23912 if delimiter_line.starts_with(delimiter_trim) {
23913 num_of_whitespaces + delimiter_trim.len() <= column as usize
23914 } else {
23915 false
23916 }
23917 };
23918
23919 let mut needs_extra_line = false;
23920 let mut extra_line_additional_indent = IndentSize::spaces(0);
23921
23922 let cursor_is_before_end_tag_if_exists = {
23923 let mut char_position = 0u32;
23924 let mut end_tag_offset = None;
23925
23926 'outer: for chunk in snapshot.text_for_range(range) {
23927 if let Some(byte_pos) = chunk.find(&**end_tag) {
23928 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23929 end_tag_offset = Some(char_position + chars_before_match);
23930 break 'outer;
23931 }
23932 char_position += chunk.chars().count() as u32;
23933 }
23934
23935 if let Some(end_tag_offset) = end_tag_offset {
23936 let cursor_is_before_end_tag = column <= end_tag_offset;
23937 if cursor_is_after_start_tag {
23938 if cursor_is_before_end_tag {
23939 needs_extra_line = true;
23940 }
23941 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23942 if cursor_is_at_start_of_end_tag {
23943 extra_line_additional_indent.len = *len;
23944 }
23945 }
23946 cursor_is_before_end_tag
23947 } else {
23948 true
23949 }
23950 };
23951
23952 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23953 && cursor_is_before_end_tag_if_exists
23954 {
23955 let additional_indent = if cursor_is_after_start_tag {
23956 IndentSize::spaces(*len)
23957 } else {
23958 IndentSize::spaces(0)
23959 };
23960
23961 *newline_config = NewlineConfig::Newline {
23962 additional_indent,
23963 extra_line_additional_indent: if needs_extra_line {
23964 Some(extra_line_additional_indent)
23965 } else {
23966 None
23967 },
23968 prevent_auto_indent: true,
23969 };
23970 Some(delimiter.clone())
23971 } else {
23972 None
23973 }
23974}
23975
23976const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
23977
23978fn list_delimiter_for_newline(
23979 start_point: &Point,
23980 buffer: &MultiBufferSnapshot,
23981 language: &LanguageScope,
23982 newline_config: &mut NewlineConfig,
23983) -> Option<Arc<str>> {
23984 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23985
23986 let num_of_whitespaces = snapshot
23987 .chars_for_range(range.clone())
23988 .take_while(|c| c.is_whitespace())
23989 .count();
23990
23991 let task_list_entries: Vec<_> = language
23992 .task_list()
23993 .into_iter()
23994 .flat_map(|config| {
23995 config
23996 .prefixes
23997 .iter()
23998 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
23999 })
24000 .collect();
24001 let unordered_list_entries: Vec<_> = language
24002 .unordered_list()
24003 .iter()
24004 .map(|marker| (marker.as_ref(), marker.as_ref()))
24005 .collect();
24006
24007 let all_entries: Vec<_> = task_list_entries
24008 .into_iter()
24009 .chain(unordered_list_entries)
24010 .collect();
24011
24012 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
24013 let candidate: String = snapshot
24014 .chars_for_range(range.clone())
24015 .skip(num_of_whitespaces)
24016 .take(max_prefix_len)
24017 .collect();
24018
24019 if let Some((prefix, continuation)) = all_entries
24020 .iter()
24021 .filter(|(prefix, _)| candidate.starts_with(*prefix))
24022 .max_by_key(|(prefix, _)| prefix.len())
24023 {
24024 let end_of_prefix = num_of_whitespaces + prefix.len();
24025 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
24026 let has_content_after_marker = snapshot
24027 .chars_for_range(range)
24028 .skip(end_of_prefix)
24029 .any(|c| !c.is_whitespace());
24030
24031 if has_content_after_marker && cursor_is_after_prefix {
24032 return Some((*continuation).into());
24033 }
24034
24035 if start_point.column as usize == end_of_prefix {
24036 if num_of_whitespaces == 0 {
24037 *newline_config = NewlineConfig::ClearCurrentLine;
24038 } else {
24039 *newline_config = NewlineConfig::UnindentCurrentLine {
24040 continuation: (*continuation).into(),
24041 };
24042 }
24043 }
24044
24045 return None;
24046 }
24047 }
24048
24049 let candidate: String = snapshot
24050 .chars_for_range(range.clone())
24051 .skip(num_of_whitespaces)
24052 .take(ORDERED_LIST_MAX_MARKER_LEN)
24053 .collect();
24054
24055 for ordered_config in language.ordered_list() {
24056 let regex = match Regex::new(&ordered_config.pattern) {
24057 Ok(r) => r,
24058 Err(_) => continue,
24059 };
24060
24061 if let Some(captures) = regex.captures(&candidate) {
24062 let full_match = captures.get(0)?;
24063 let marker_len = full_match.len();
24064 let end_of_prefix = num_of_whitespaces + marker_len;
24065 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
24066
24067 let has_content_after_marker = snapshot
24068 .chars_for_range(range)
24069 .skip(end_of_prefix)
24070 .any(|c| !c.is_whitespace());
24071
24072 if has_content_after_marker && cursor_is_after_prefix {
24073 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
24074 let continuation = ordered_config
24075 .format
24076 .replace("{1}", &(number + 1).to_string());
24077 return Some(continuation.into());
24078 }
24079
24080 if start_point.column as usize == end_of_prefix {
24081 let continuation = ordered_config.format.replace("{1}", "1");
24082 if num_of_whitespaces == 0 {
24083 *newline_config = NewlineConfig::ClearCurrentLine;
24084 } else {
24085 *newline_config = NewlineConfig::UnindentCurrentLine {
24086 continuation: continuation.into(),
24087 };
24088 }
24089 }
24090
24091 return None;
24092 }
24093 }
24094
24095 None
24096}
24097
24098fn is_list_prefix_row(
24099 row: MultiBufferRow,
24100 buffer: &MultiBufferSnapshot,
24101 language: &LanguageScope,
24102) -> bool {
24103 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
24104 return false;
24105 };
24106
24107 let num_of_whitespaces = snapshot
24108 .chars_for_range(range.clone())
24109 .take_while(|c| c.is_whitespace())
24110 .count();
24111
24112 let task_list_prefixes: Vec<_> = language
24113 .task_list()
24114 .into_iter()
24115 .flat_map(|config| {
24116 config
24117 .prefixes
24118 .iter()
24119 .map(|p| p.as_ref())
24120 .collect::<Vec<_>>()
24121 })
24122 .collect();
24123 let unordered_list_markers: Vec<_> = language
24124 .unordered_list()
24125 .iter()
24126 .map(|marker| marker.as_ref())
24127 .collect();
24128 let all_prefixes: Vec<_> = task_list_prefixes
24129 .into_iter()
24130 .chain(unordered_list_markers)
24131 .collect();
24132 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
24133 let candidate: String = snapshot
24134 .chars_for_range(range.clone())
24135 .skip(num_of_whitespaces)
24136 .take(max_prefix_len)
24137 .collect();
24138 if all_prefixes
24139 .iter()
24140 .any(|prefix| candidate.starts_with(*prefix))
24141 {
24142 return true;
24143 }
24144 }
24145
24146 let ordered_list_candidate: String = snapshot
24147 .chars_for_range(range)
24148 .skip(num_of_whitespaces)
24149 .take(ORDERED_LIST_MAX_MARKER_LEN)
24150 .collect();
24151 for ordered_config in language.ordered_list() {
24152 let regex = match Regex::new(&ordered_config.pattern) {
24153 Ok(r) => r,
24154 Err(_) => continue,
24155 };
24156 if let Some(captures) = regex.captures(&ordered_list_candidate) {
24157 return captures.get(0).is_some();
24158 }
24159 }
24160
24161 false
24162}
24163
24164#[derive(Debug)]
24165enum NewlineConfig {
24166 /// Insert newline with optional additional indent and optional extra blank line
24167 Newline {
24168 additional_indent: IndentSize,
24169 extra_line_additional_indent: Option<IndentSize>,
24170 prevent_auto_indent: bool,
24171 },
24172 /// Clear the current line
24173 ClearCurrentLine,
24174 /// Unindent the current line and add continuation
24175 UnindentCurrentLine { continuation: Arc<str> },
24176}
24177
24178impl NewlineConfig {
24179 fn has_extra_line(&self) -> bool {
24180 matches!(
24181 self,
24182 Self::Newline {
24183 extra_line_additional_indent: Some(_),
24184 ..
24185 }
24186 )
24187 }
24188
24189 fn insert_extra_newline_brackets(
24190 buffer: &MultiBufferSnapshot,
24191 range: Range<MultiBufferOffset>,
24192 language: &language::LanguageScope,
24193 ) -> bool {
24194 let leading_whitespace_len = buffer
24195 .reversed_chars_at(range.start)
24196 .take_while(|c| c.is_whitespace() && *c != '\n')
24197 .map(|c| c.len_utf8())
24198 .sum::<usize>();
24199 let trailing_whitespace_len = buffer
24200 .chars_at(range.end)
24201 .take_while(|c| c.is_whitespace() && *c != '\n')
24202 .map(|c| c.len_utf8())
24203 .sum::<usize>();
24204 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
24205
24206 language.brackets().any(|(pair, enabled)| {
24207 let pair_start = pair.start.trim_end();
24208 let pair_end = pair.end.trim_start();
24209
24210 enabled
24211 && pair.newline
24212 && buffer.contains_str_at(range.end, pair_end)
24213 && buffer.contains_str_at(
24214 range.start.saturating_sub_usize(pair_start.len()),
24215 pair_start,
24216 )
24217 })
24218 }
24219
24220 fn insert_extra_newline_tree_sitter(
24221 buffer: &MultiBufferSnapshot,
24222 range: Range<MultiBufferOffset>,
24223 ) -> bool {
24224 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
24225 [(buffer, range, _)] => (*buffer, range.clone()),
24226 _ => return false,
24227 };
24228 let pair = {
24229 let mut result: Option<BracketMatch<usize>> = None;
24230
24231 for pair in buffer
24232 .all_bracket_ranges(range.start.0..range.end.0)
24233 .filter(move |pair| {
24234 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
24235 })
24236 {
24237 let len = pair.close_range.end - pair.open_range.start;
24238
24239 if let Some(existing) = &result {
24240 let existing_len = existing.close_range.end - existing.open_range.start;
24241 if len > existing_len {
24242 continue;
24243 }
24244 }
24245
24246 result = Some(pair);
24247 }
24248
24249 result
24250 };
24251 let Some(pair) = pair else {
24252 return false;
24253 };
24254 pair.newline_only
24255 && buffer
24256 .chars_for_range(pair.open_range.end..range.start.0)
24257 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
24258 .all(|c| c.is_whitespace() && c != '\n')
24259 }
24260}
24261
24262fn update_uncommitted_diff_for_buffer(
24263 editor: Entity<Editor>,
24264 project: &Entity<Project>,
24265 buffers: impl IntoIterator<Item = Entity<Buffer>>,
24266 buffer: Entity<MultiBuffer>,
24267 cx: &mut App,
24268) -> Task<()> {
24269 let mut tasks = Vec::new();
24270 project.update(cx, |project, cx| {
24271 for buffer in buffers {
24272 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
24273 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
24274 }
24275 }
24276 });
24277 cx.spawn(async move |cx| {
24278 let diffs = future::join_all(tasks).await;
24279 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
24280 return;
24281 }
24282
24283 buffer.update(cx, |buffer, cx| {
24284 for diff in diffs.into_iter().flatten() {
24285 buffer.add_diff(diff, cx);
24286 }
24287 });
24288 })
24289}
24290
24291fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
24292 let tab_size = tab_size.get() as usize;
24293 let mut width = offset;
24294
24295 for ch in text.chars() {
24296 width += if ch == '\t' {
24297 tab_size - (width % tab_size)
24298 } else {
24299 1
24300 };
24301 }
24302
24303 width - offset
24304}
24305
24306#[cfg(test)]
24307mod tests {
24308 use super::*;
24309
24310 #[test]
24311 fn test_string_size_with_expanded_tabs() {
24312 let nz = |val| NonZeroU32::new(val).unwrap();
24313 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
24314 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
24315 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
24316 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
24317 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
24318 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
24319 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
24320 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
24321 }
24322}
24323
24324/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
24325struct WordBreakingTokenizer<'a> {
24326 input: &'a str,
24327}
24328
24329impl<'a> WordBreakingTokenizer<'a> {
24330 fn new(input: &'a str) -> Self {
24331 Self { input }
24332 }
24333}
24334
24335fn is_char_ideographic(ch: char) -> bool {
24336 use unicode_script::Script::*;
24337 use unicode_script::UnicodeScript;
24338 matches!(ch.script(), Han | Tangut | Yi)
24339}
24340
24341fn is_grapheme_ideographic(text: &str) -> bool {
24342 text.chars().any(is_char_ideographic)
24343}
24344
24345fn is_grapheme_whitespace(text: &str) -> bool {
24346 text.chars().any(|x| x.is_whitespace())
24347}
24348
24349fn should_stay_with_preceding_ideograph(text: &str) -> bool {
24350 text.chars()
24351 .next()
24352 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
24353}
24354
24355#[derive(PartialEq, Eq, Debug, Clone, Copy)]
24356enum WordBreakToken<'a> {
24357 Word { token: &'a str, grapheme_len: usize },
24358 InlineWhitespace { token: &'a str, grapheme_len: usize },
24359 Newline,
24360}
24361
24362impl<'a> Iterator for WordBreakingTokenizer<'a> {
24363 /// Yields a span, the count of graphemes in the token, and whether it was
24364 /// whitespace. Note that it also breaks at word boundaries.
24365 type Item = WordBreakToken<'a>;
24366
24367 fn next(&mut self) -> Option<Self::Item> {
24368 use unicode_segmentation::UnicodeSegmentation;
24369 if self.input.is_empty() {
24370 return None;
24371 }
24372
24373 let mut iter = self.input.graphemes(true).peekable();
24374 let mut offset = 0;
24375 let mut grapheme_len = 0;
24376 if let Some(first_grapheme) = iter.next() {
24377 let is_newline = first_grapheme == "\n";
24378 let is_whitespace = is_grapheme_whitespace(first_grapheme);
24379 offset += first_grapheme.len();
24380 grapheme_len += 1;
24381 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
24382 if let Some(grapheme) = iter.peek().copied()
24383 && should_stay_with_preceding_ideograph(grapheme)
24384 {
24385 offset += grapheme.len();
24386 grapheme_len += 1;
24387 }
24388 } else {
24389 let mut words = self.input[offset..].split_word_bound_indices().peekable();
24390 let mut next_word_bound = words.peek().copied();
24391 if next_word_bound.is_some_and(|(i, _)| i == 0) {
24392 next_word_bound = words.next();
24393 }
24394 while let Some(grapheme) = iter.peek().copied() {
24395 if next_word_bound.is_some_and(|(i, _)| i == offset) {
24396 break;
24397 };
24398 if is_grapheme_whitespace(grapheme) != is_whitespace
24399 || (grapheme == "\n") != is_newline
24400 {
24401 break;
24402 };
24403 offset += grapheme.len();
24404 grapheme_len += 1;
24405 iter.next();
24406 }
24407 }
24408 let token = &self.input[..offset];
24409 self.input = &self.input[offset..];
24410 if token == "\n" {
24411 Some(WordBreakToken::Newline)
24412 } else if is_whitespace {
24413 Some(WordBreakToken::InlineWhitespace {
24414 token,
24415 grapheme_len,
24416 })
24417 } else {
24418 Some(WordBreakToken::Word {
24419 token,
24420 grapheme_len,
24421 })
24422 }
24423 } else {
24424 None
24425 }
24426 }
24427}
24428
24429#[test]
24430fn test_word_breaking_tokenizer() {
24431 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
24432 ("", &[]),
24433 (" ", &[whitespace(" ", 2)]),
24434 ("Ʒ", &[word("Ʒ", 1)]),
24435 ("Ǽ", &[word("Ǽ", 1)]),
24436 ("⋑", &[word("⋑", 1)]),
24437 ("⋑⋑", &[word("⋑⋑", 2)]),
24438 (
24439 "原理,进而",
24440 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
24441 ),
24442 (
24443 "hello world",
24444 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
24445 ),
24446 (
24447 "hello, world",
24448 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
24449 ),
24450 (
24451 " hello world",
24452 &[
24453 whitespace(" ", 2),
24454 word("hello", 5),
24455 whitespace(" ", 1),
24456 word("world", 5),
24457 ],
24458 ),
24459 (
24460 "这是什么 \n 钢笔",
24461 &[
24462 word("这", 1),
24463 word("是", 1),
24464 word("什", 1),
24465 word("么", 1),
24466 whitespace(" ", 1),
24467 newline(),
24468 whitespace(" ", 1),
24469 word("钢", 1),
24470 word("笔", 1),
24471 ],
24472 ),
24473 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
24474 ];
24475
24476 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24477 WordBreakToken::Word {
24478 token,
24479 grapheme_len,
24480 }
24481 }
24482
24483 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24484 WordBreakToken::InlineWhitespace {
24485 token,
24486 grapheme_len,
24487 }
24488 }
24489
24490 fn newline() -> WordBreakToken<'static> {
24491 WordBreakToken::Newline
24492 }
24493
24494 for (input, result) in tests {
24495 assert_eq!(
24496 WordBreakingTokenizer::new(input)
24497 .collect::<Vec<_>>()
24498 .as_slice(),
24499 *result,
24500 );
24501 }
24502}
24503
24504fn wrap_with_prefix(
24505 first_line_prefix: String,
24506 subsequent_lines_prefix: String,
24507 unwrapped_text: String,
24508 wrap_column: usize,
24509 tab_size: NonZeroU32,
24510 preserve_existing_whitespace: bool,
24511) -> String {
24512 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
24513 let subsequent_lines_prefix_len =
24514 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
24515 let mut wrapped_text = String::new();
24516 let mut current_line = first_line_prefix;
24517 let mut is_first_line = true;
24518
24519 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
24520 let mut current_line_len = first_line_prefix_len;
24521 let mut in_whitespace = false;
24522 for token in tokenizer {
24523 let have_preceding_whitespace = in_whitespace;
24524 match token {
24525 WordBreakToken::Word {
24526 token,
24527 grapheme_len,
24528 } => {
24529 in_whitespace = false;
24530 let current_prefix_len = if is_first_line {
24531 first_line_prefix_len
24532 } else {
24533 subsequent_lines_prefix_len
24534 };
24535 if current_line_len + grapheme_len > wrap_column
24536 && current_line_len != current_prefix_len
24537 {
24538 wrapped_text.push_str(current_line.trim_end());
24539 wrapped_text.push('\n');
24540 is_first_line = false;
24541 current_line = subsequent_lines_prefix.clone();
24542 current_line_len = subsequent_lines_prefix_len;
24543 }
24544 current_line.push_str(token);
24545 current_line_len += grapheme_len;
24546 }
24547 WordBreakToken::InlineWhitespace {
24548 mut token,
24549 mut grapheme_len,
24550 } => {
24551 in_whitespace = true;
24552 if have_preceding_whitespace && !preserve_existing_whitespace {
24553 continue;
24554 }
24555 if !preserve_existing_whitespace {
24556 // Keep a single whitespace grapheme as-is
24557 if let Some(first) =
24558 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
24559 {
24560 token = first;
24561 } else {
24562 token = " ";
24563 }
24564 grapheme_len = 1;
24565 }
24566 let current_prefix_len = if is_first_line {
24567 first_line_prefix_len
24568 } else {
24569 subsequent_lines_prefix_len
24570 };
24571 if current_line_len + grapheme_len > wrap_column {
24572 wrapped_text.push_str(current_line.trim_end());
24573 wrapped_text.push('\n');
24574 is_first_line = false;
24575 current_line = subsequent_lines_prefix.clone();
24576 current_line_len = subsequent_lines_prefix_len;
24577 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
24578 current_line.push_str(token);
24579 current_line_len += grapheme_len;
24580 }
24581 }
24582 WordBreakToken::Newline => {
24583 in_whitespace = true;
24584 let current_prefix_len = if is_first_line {
24585 first_line_prefix_len
24586 } else {
24587 subsequent_lines_prefix_len
24588 };
24589 if preserve_existing_whitespace {
24590 wrapped_text.push_str(current_line.trim_end());
24591 wrapped_text.push('\n');
24592 is_first_line = false;
24593 current_line = subsequent_lines_prefix.clone();
24594 current_line_len = subsequent_lines_prefix_len;
24595 } else if have_preceding_whitespace {
24596 continue;
24597 } else if current_line_len + 1 > wrap_column
24598 && current_line_len != current_prefix_len
24599 {
24600 wrapped_text.push_str(current_line.trim_end());
24601 wrapped_text.push('\n');
24602 is_first_line = false;
24603 current_line = subsequent_lines_prefix.clone();
24604 current_line_len = subsequent_lines_prefix_len;
24605 } else if current_line_len != current_prefix_len {
24606 current_line.push(' ');
24607 current_line_len += 1;
24608 }
24609 }
24610 }
24611 }
24612
24613 if !current_line.is_empty() {
24614 wrapped_text.push_str(¤t_line);
24615 }
24616 wrapped_text
24617}
24618
24619#[test]
24620fn test_wrap_with_prefix() {
24621 assert_eq!(
24622 wrap_with_prefix(
24623 "# ".to_string(),
24624 "# ".to_string(),
24625 "abcdefg".to_string(),
24626 4,
24627 NonZeroU32::new(4).unwrap(),
24628 false,
24629 ),
24630 "# abcdefg"
24631 );
24632 assert_eq!(
24633 wrap_with_prefix(
24634 "".to_string(),
24635 "".to_string(),
24636 "\thello world".to_string(),
24637 8,
24638 NonZeroU32::new(4).unwrap(),
24639 false,
24640 ),
24641 "hello\nworld"
24642 );
24643 assert_eq!(
24644 wrap_with_prefix(
24645 "// ".to_string(),
24646 "// ".to_string(),
24647 "xx \nyy zz aa bb cc".to_string(),
24648 12,
24649 NonZeroU32::new(4).unwrap(),
24650 false,
24651 ),
24652 "// xx yy zz\n// aa bb cc"
24653 );
24654 assert_eq!(
24655 wrap_with_prefix(
24656 String::new(),
24657 String::new(),
24658 "这是什么 \n 钢笔".to_string(),
24659 3,
24660 NonZeroU32::new(4).unwrap(),
24661 false,
24662 ),
24663 "这是什\n么 钢\n笔"
24664 );
24665 assert_eq!(
24666 wrap_with_prefix(
24667 String::new(),
24668 String::new(),
24669 format!("foo{}bar", '\u{2009}'), // thin space
24670 80,
24671 NonZeroU32::new(4).unwrap(),
24672 false,
24673 ),
24674 format!("foo{}bar", '\u{2009}')
24675 );
24676}
24677
24678pub trait CollaborationHub {
24679 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24680 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24681 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24682}
24683
24684impl CollaborationHub for Entity<Project> {
24685 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24686 self.read(cx).collaborators()
24687 }
24688
24689 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24690 self.read(cx).user_store().read(cx).participant_indices()
24691 }
24692
24693 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24694 let this = self.read(cx);
24695 let user_ids = this.collaborators().values().map(|c| c.user_id);
24696 this.user_store().read(cx).participant_names(user_ids, cx)
24697 }
24698}
24699
24700pub trait SemanticsProvider {
24701 fn hover(
24702 &self,
24703 buffer: &Entity<Buffer>,
24704 position: text::Anchor,
24705 cx: &mut App,
24706 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24707
24708 fn inline_values(
24709 &self,
24710 buffer_handle: Entity<Buffer>,
24711 range: Range<text::Anchor>,
24712 cx: &mut App,
24713 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24714
24715 fn applicable_inlay_chunks(
24716 &self,
24717 buffer: &Entity<Buffer>,
24718 ranges: &[Range<text::Anchor>],
24719 cx: &mut App,
24720 ) -> Vec<Range<BufferRow>>;
24721
24722 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24723
24724 fn inlay_hints(
24725 &self,
24726 invalidate: InvalidationStrategy,
24727 buffer: Entity<Buffer>,
24728 ranges: Vec<Range<text::Anchor>>,
24729 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24730 cx: &mut App,
24731 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24732
24733 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24734
24735 fn document_highlights(
24736 &self,
24737 buffer: &Entity<Buffer>,
24738 position: text::Anchor,
24739 cx: &mut App,
24740 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24741
24742 fn definitions(
24743 &self,
24744 buffer: &Entity<Buffer>,
24745 position: text::Anchor,
24746 kind: GotoDefinitionKind,
24747 cx: &mut App,
24748 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24749
24750 fn range_for_rename(
24751 &self,
24752 buffer: &Entity<Buffer>,
24753 position: text::Anchor,
24754 cx: &mut App,
24755 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24756
24757 fn perform_rename(
24758 &self,
24759 buffer: &Entity<Buffer>,
24760 position: text::Anchor,
24761 new_name: String,
24762 cx: &mut App,
24763 ) -> Option<Task<Result<ProjectTransaction>>>;
24764}
24765
24766pub trait CompletionProvider {
24767 fn completions(
24768 &self,
24769 excerpt_id: ExcerptId,
24770 buffer: &Entity<Buffer>,
24771 buffer_position: text::Anchor,
24772 trigger: CompletionContext,
24773 window: &mut Window,
24774 cx: &mut Context<Editor>,
24775 ) -> Task<Result<Vec<CompletionResponse>>>;
24776
24777 fn resolve_completions(
24778 &self,
24779 _buffer: Entity<Buffer>,
24780 _completion_indices: Vec<usize>,
24781 _completions: Rc<RefCell<Box<[Completion]>>>,
24782 _cx: &mut Context<Editor>,
24783 ) -> Task<Result<bool>> {
24784 Task::ready(Ok(false))
24785 }
24786
24787 fn apply_additional_edits_for_completion(
24788 &self,
24789 _buffer: Entity<Buffer>,
24790 _completions: Rc<RefCell<Box<[Completion]>>>,
24791 _completion_index: usize,
24792 _push_to_history: bool,
24793 _cx: &mut Context<Editor>,
24794 ) -> Task<Result<Option<language::Transaction>>> {
24795 Task::ready(Ok(None))
24796 }
24797
24798 fn is_completion_trigger(
24799 &self,
24800 buffer: &Entity<Buffer>,
24801 position: language::Anchor,
24802 text: &str,
24803 trigger_in_words: bool,
24804 cx: &mut Context<Editor>,
24805 ) -> bool;
24806
24807 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24808
24809 fn sort_completions(&self) -> bool {
24810 true
24811 }
24812
24813 fn filter_completions(&self) -> bool {
24814 true
24815 }
24816
24817 fn show_snippets(&self) -> bool {
24818 false
24819 }
24820}
24821
24822pub trait CodeActionProvider {
24823 fn id(&self) -> Arc<str>;
24824
24825 fn code_actions(
24826 &self,
24827 buffer: &Entity<Buffer>,
24828 range: Range<text::Anchor>,
24829 window: &mut Window,
24830 cx: &mut App,
24831 ) -> Task<Result<Vec<CodeAction>>>;
24832
24833 fn apply_code_action(
24834 &self,
24835 buffer_handle: Entity<Buffer>,
24836 action: CodeAction,
24837 excerpt_id: ExcerptId,
24838 push_to_history: bool,
24839 window: &mut Window,
24840 cx: &mut App,
24841 ) -> Task<Result<ProjectTransaction>>;
24842}
24843
24844impl CodeActionProvider for Entity<Project> {
24845 fn id(&self) -> Arc<str> {
24846 "project".into()
24847 }
24848
24849 fn code_actions(
24850 &self,
24851 buffer: &Entity<Buffer>,
24852 range: Range<text::Anchor>,
24853 _window: &mut Window,
24854 cx: &mut App,
24855 ) -> Task<Result<Vec<CodeAction>>> {
24856 self.update(cx, |project, cx| {
24857 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24858 let code_actions = project.code_actions(buffer, range, None, cx);
24859 cx.background_spawn(async move {
24860 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24861 Ok(code_lens_actions
24862 .context("code lens fetch")?
24863 .into_iter()
24864 .flatten()
24865 .chain(
24866 code_actions
24867 .context("code action fetch")?
24868 .into_iter()
24869 .flatten(),
24870 )
24871 .collect())
24872 })
24873 })
24874 }
24875
24876 fn apply_code_action(
24877 &self,
24878 buffer_handle: Entity<Buffer>,
24879 action: CodeAction,
24880 _excerpt_id: ExcerptId,
24881 push_to_history: bool,
24882 _window: &mut Window,
24883 cx: &mut App,
24884 ) -> Task<Result<ProjectTransaction>> {
24885 self.update(cx, |project, cx| {
24886 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24887 })
24888 }
24889}
24890
24891fn snippet_completions(
24892 project: &Project,
24893 buffer: &Entity<Buffer>,
24894 buffer_anchor: text::Anchor,
24895 classifier: CharClassifier,
24896 cx: &mut App,
24897) -> Task<Result<CompletionResponse>> {
24898 let languages = buffer.read(cx).languages_at(buffer_anchor);
24899 let snippet_store = project.snippets().read(cx);
24900
24901 let scopes: Vec<_> = languages
24902 .iter()
24903 .filter_map(|language| {
24904 let language_name = language.lsp_id();
24905 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24906
24907 if snippets.is_empty() {
24908 None
24909 } else {
24910 Some((language.default_scope(), snippets))
24911 }
24912 })
24913 .collect();
24914
24915 if scopes.is_empty() {
24916 return Task::ready(Ok(CompletionResponse {
24917 completions: vec![],
24918 display_options: CompletionDisplayOptions::default(),
24919 is_incomplete: false,
24920 }));
24921 }
24922
24923 let snapshot = buffer.read(cx).text_snapshot();
24924 let executor = cx.background_executor().clone();
24925
24926 cx.background_spawn(async move {
24927 let is_word_char = |c| classifier.is_word(c);
24928
24929 let mut is_incomplete = false;
24930 let mut completions: Vec<Completion> = Vec::new();
24931
24932 const MAX_PREFIX_LEN: usize = 128;
24933 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24934 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24935 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24936
24937 let max_buffer_window: String = snapshot
24938 .text_for_range(window_start..buffer_offset)
24939 .collect();
24940
24941 if max_buffer_window.is_empty() {
24942 return Ok(CompletionResponse {
24943 completions: vec![],
24944 display_options: CompletionDisplayOptions::default(),
24945 is_incomplete: true,
24946 });
24947 }
24948
24949 for (_scope, snippets) in scopes.into_iter() {
24950 // Sort snippets by word count to match longer snippet prefixes first.
24951 let mut sorted_snippet_candidates = snippets
24952 .iter()
24953 .enumerate()
24954 .flat_map(|(snippet_ix, snippet)| {
24955 snippet
24956 .prefix
24957 .iter()
24958 .enumerate()
24959 .map(move |(prefix_ix, prefix)| {
24960 let word_count =
24961 snippet_candidate_suffixes(prefix, is_word_char).count();
24962 ((snippet_ix, prefix_ix), prefix, word_count)
24963 })
24964 })
24965 .collect_vec();
24966 sorted_snippet_candidates
24967 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24968
24969 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24970
24971 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24972 .take(
24973 sorted_snippet_candidates
24974 .first()
24975 .map(|(_, _, word_count)| *word_count)
24976 .unwrap_or_default(),
24977 )
24978 .collect_vec();
24979
24980 const MAX_RESULTS: usize = 100;
24981 // Each match also remembers how many characters from the buffer it consumed
24982 let mut matches: Vec<(StringMatch, usize)> = vec![];
24983
24984 let mut snippet_list_cutoff_index = 0;
24985 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24986 let word_count = buffer_index + 1;
24987 // Increase `snippet_list_cutoff_index` until we have all of the
24988 // snippets with sufficiently many words.
24989 while sorted_snippet_candidates
24990 .get(snippet_list_cutoff_index)
24991 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24992 *snippet_word_count >= word_count
24993 })
24994 {
24995 snippet_list_cutoff_index += 1;
24996 }
24997
24998 // Take only the candidates with at least `word_count` many words
24999 let snippet_candidates_at_word_len =
25000 &sorted_snippet_candidates[..snippet_list_cutoff_index];
25001
25002 let candidates = snippet_candidates_at_word_len
25003 .iter()
25004 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
25005 .enumerate() // index in `sorted_snippet_candidates`
25006 // First char must match
25007 .filter(|(_ix, prefix)| {
25008 itertools::equal(
25009 prefix
25010 .chars()
25011 .next()
25012 .into_iter()
25013 .flat_map(|c| c.to_lowercase()),
25014 buffer_window
25015 .chars()
25016 .next()
25017 .into_iter()
25018 .flat_map(|c| c.to_lowercase()),
25019 )
25020 })
25021 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
25022 .collect::<Vec<StringMatchCandidate>>();
25023
25024 matches.extend(
25025 fuzzy::match_strings(
25026 &candidates,
25027 &buffer_window,
25028 buffer_window.chars().any(|c| c.is_uppercase()),
25029 true,
25030 MAX_RESULTS - matches.len(), // always prioritize longer snippets
25031 &Default::default(),
25032 executor.clone(),
25033 )
25034 .await
25035 .into_iter()
25036 .map(|string_match| (string_match, buffer_window.len())),
25037 );
25038
25039 if matches.len() >= MAX_RESULTS {
25040 break;
25041 }
25042 }
25043
25044 let to_lsp = |point: &text::Anchor| {
25045 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
25046 point_to_lsp(end)
25047 };
25048 let lsp_end = to_lsp(&buffer_anchor);
25049
25050 if matches.len() >= MAX_RESULTS {
25051 is_incomplete = true;
25052 }
25053
25054 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
25055 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
25056 sorted_snippet_candidates[string_match.candidate_id];
25057 let snippet = &snippets[snippet_index];
25058 let start = buffer_offset - buffer_window_len;
25059 let start = snapshot.anchor_before(start);
25060 let range = start..buffer_anchor;
25061 let lsp_start = to_lsp(&start);
25062 let lsp_range = lsp::Range {
25063 start: lsp_start,
25064 end: lsp_end,
25065 };
25066 Completion {
25067 replace_range: range,
25068 new_text: snippet.body.clone(),
25069 source: CompletionSource::Lsp {
25070 insert_range: None,
25071 server_id: LanguageServerId(usize::MAX),
25072 resolved: true,
25073 lsp_completion: Box::new(lsp::CompletionItem {
25074 label: snippet.prefix.first().unwrap().clone(),
25075 kind: Some(CompletionItemKind::SNIPPET),
25076 label_details: snippet.description.as_ref().map(|description| {
25077 lsp::CompletionItemLabelDetails {
25078 detail: Some(description.clone()),
25079 description: None,
25080 }
25081 }),
25082 insert_text_format: Some(InsertTextFormat::SNIPPET),
25083 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25084 lsp::InsertReplaceEdit {
25085 new_text: snippet.body.clone(),
25086 insert: lsp_range,
25087 replace: lsp_range,
25088 },
25089 )),
25090 filter_text: Some(snippet.body.clone()),
25091 sort_text: Some(char::MAX.to_string()),
25092 ..lsp::CompletionItem::default()
25093 }),
25094 lsp_defaults: None,
25095 },
25096 label: CodeLabel {
25097 text: matching_prefix.clone(),
25098 runs: Vec::new(),
25099 filter_range: 0..matching_prefix.len(),
25100 },
25101 icon_path: None,
25102 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
25103 single_line: snippet.name.clone().into(),
25104 plain_text: snippet
25105 .description
25106 .clone()
25107 .map(|description| description.into()),
25108 }),
25109 insert_text_mode: None,
25110 confirm: None,
25111 match_start: Some(start),
25112 snippet_deduplication_key: Some((snippet_index, prefix_index)),
25113 }
25114 }));
25115 }
25116
25117 Ok(CompletionResponse {
25118 completions,
25119 display_options: CompletionDisplayOptions::default(),
25120 is_incomplete,
25121 })
25122 })
25123}
25124
25125impl CompletionProvider for Entity<Project> {
25126 fn completions(
25127 &self,
25128 _excerpt_id: ExcerptId,
25129 buffer: &Entity<Buffer>,
25130 buffer_position: text::Anchor,
25131 options: CompletionContext,
25132 _window: &mut Window,
25133 cx: &mut Context<Editor>,
25134 ) -> Task<Result<Vec<CompletionResponse>>> {
25135 self.update(cx, |project, cx| {
25136 let task = project.completions(buffer, buffer_position, options, cx);
25137 cx.background_spawn(task)
25138 })
25139 }
25140
25141 fn resolve_completions(
25142 &self,
25143 buffer: Entity<Buffer>,
25144 completion_indices: Vec<usize>,
25145 completions: Rc<RefCell<Box<[Completion]>>>,
25146 cx: &mut Context<Editor>,
25147 ) -> Task<Result<bool>> {
25148 self.update(cx, |project, cx| {
25149 project.lsp_store().update(cx, |lsp_store, cx| {
25150 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
25151 })
25152 })
25153 }
25154
25155 fn apply_additional_edits_for_completion(
25156 &self,
25157 buffer: Entity<Buffer>,
25158 completions: Rc<RefCell<Box<[Completion]>>>,
25159 completion_index: usize,
25160 push_to_history: bool,
25161 cx: &mut Context<Editor>,
25162 ) -> Task<Result<Option<language::Transaction>>> {
25163 self.update(cx, |project, cx| {
25164 project.lsp_store().update(cx, |lsp_store, cx| {
25165 lsp_store.apply_additional_edits_for_completion(
25166 buffer,
25167 completions,
25168 completion_index,
25169 push_to_history,
25170 cx,
25171 )
25172 })
25173 })
25174 }
25175
25176 fn is_completion_trigger(
25177 &self,
25178 buffer: &Entity<Buffer>,
25179 position: language::Anchor,
25180 text: &str,
25181 trigger_in_words: bool,
25182 cx: &mut Context<Editor>,
25183 ) -> bool {
25184 let mut chars = text.chars();
25185 let char = if let Some(char) = chars.next() {
25186 char
25187 } else {
25188 return false;
25189 };
25190 if chars.next().is_some() {
25191 return false;
25192 }
25193
25194 let buffer = buffer.read(cx);
25195 let snapshot = buffer.snapshot();
25196 let classifier = snapshot
25197 .char_classifier_at(position)
25198 .scope_context(Some(CharScopeContext::Completion));
25199 if trigger_in_words && classifier.is_word(char) {
25200 return true;
25201 }
25202
25203 buffer.completion_triggers().contains(text)
25204 }
25205
25206 fn show_snippets(&self) -> bool {
25207 true
25208 }
25209}
25210
25211impl SemanticsProvider for Entity<Project> {
25212 fn hover(
25213 &self,
25214 buffer: &Entity<Buffer>,
25215 position: text::Anchor,
25216 cx: &mut App,
25217 ) -> Option<Task<Option<Vec<project::Hover>>>> {
25218 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
25219 }
25220
25221 fn document_highlights(
25222 &self,
25223 buffer: &Entity<Buffer>,
25224 position: text::Anchor,
25225 cx: &mut App,
25226 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
25227 Some(self.update(cx, |project, cx| {
25228 project.document_highlights(buffer, position, cx)
25229 }))
25230 }
25231
25232 fn definitions(
25233 &self,
25234 buffer: &Entity<Buffer>,
25235 position: text::Anchor,
25236 kind: GotoDefinitionKind,
25237 cx: &mut App,
25238 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
25239 Some(self.update(cx, |project, cx| match kind {
25240 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
25241 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
25242 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
25243 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
25244 }))
25245 }
25246
25247 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
25248 self.update(cx, |project, cx| {
25249 if project
25250 .active_debug_session(cx)
25251 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
25252 {
25253 return true;
25254 }
25255
25256 buffer.update(cx, |buffer, cx| {
25257 project.any_language_server_supports_inlay_hints(buffer, cx)
25258 })
25259 })
25260 }
25261
25262 fn inline_values(
25263 &self,
25264 buffer_handle: Entity<Buffer>,
25265 range: Range<text::Anchor>,
25266 cx: &mut App,
25267 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
25268 self.update(cx, |project, cx| {
25269 let (session, active_stack_frame) = project.active_debug_session(cx)?;
25270
25271 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
25272 })
25273 }
25274
25275 fn applicable_inlay_chunks(
25276 &self,
25277 buffer: &Entity<Buffer>,
25278 ranges: &[Range<text::Anchor>],
25279 cx: &mut App,
25280 ) -> Vec<Range<BufferRow>> {
25281 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25282 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
25283 })
25284 }
25285
25286 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
25287 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
25288 lsp_store.invalidate_inlay_hints(for_buffers)
25289 });
25290 }
25291
25292 fn inlay_hints(
25293 &self,
25294 invalidate: InvalidationStrategy,
25295 buffer: Entity<Buffer>,
25296 ranges: Vec<Range<text::Anchor>>,
25297 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
25298 cx: &mut App,
25299 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
25300 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25301 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
25302 }))
25303 }
25304
25305 fn range_for_rename(
25306 &self,
25307 buffer: &Entity<Buffer>,
25308 position: text::Anchor,
25309 cx: &mut App,
25310 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
25311 Some(self.update(cx, |project, cx| {
25312 let buffer = buffer.clone();
25313 let task = project.prepare_rename(buffer.clone(), position, cx);
25314 cx.spawn(async move |_, cx| {
25315 Ok(match task.await? {
25316 PrepareRenameResponse::Success(range) => Some(range),
25317 PrepareRenameResponse::InvalidPosition => None,
25318 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
25319 // Fallback on using TreeSitter info to determine identifier range
25320 buffer.read_with(cx, |buffer, _| {
25321 let snapshot = buffer.snapshot();
25322 let (range, kind) = snapshot.surrounding_word(position, None);
25323 if kind != Some(CharKind::Word) {
25324 return None;
25325 }
25326 Some(
25327 snapshot.anchor_before(range.start)
25328 ..snapshot.anchor_after(range.end),
25329 )
25330 })
25331 }
25332 })
25333 })
25334 }))
25335 }
25336
25337 fn perform_rename(
25338 &self,
25339 buffer: &Entity<Buffer>,
25340 position: text::Anchor,
25341 new_name: String,
25342 cx: &mut App,
25343 ) -> Option<Task<Result<ProjectTransaction>>> {
25344 Some(self.update(cx, |project, cx| {
25345 project.perform_rename(buffer.clone(), position, new_name, cx)
25346 }))
25347 }
25348}
25349
25350fn consume_contiguous_rows(
25351 contiguous_row_selections: &mut Vec<Selection<Point>>,
25352 selection: &Selection<Point>,
25353 display_map: &DisplaySnapshot,
25354 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
25355) -> (MultiBufferRow, MultiBufferRow) {
25356 contiguous_row_selections.push(selection.clone());
25357 let start_row = starting_row(selection, display_map);
25358 let mut end_row = ending_row(selection, display_map);
25359
25360 while let Some(next_selection) = selections.peek() {
25361 if next_selection.start.row <= end_row.0 {
25362 end_row = ending_row(next_selection, display_map);
25363 contiguous_row_selections.push(selections.next().unwrap().clone());
25364 } else {
25365 break;
25366 }
25367 }
25368 (start_row, end_row)
25369}
25370
25371fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25372 if selection.start.column > 0 {
25373 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
25374 } else {
25375 MultiBufferRow(selection.start.row)
25376 }
25377}
25378
25379fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25380 if next_selection.end.column > 0 || next_selection.is_empty() {
25381 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
25382 } else {
25383 MultiBufferRow(next_selection.end.row)
25384 }
25385}
25386
25387impl EditorSnapshot {
25388 pub fn remote_selections_in_range<'a>(
25389 &'a self,
25390 range: &'a Range<Anchor>,
25391 collaboration_hub: &dyn CollaborationHub,
25392 cx: &'a App,
25393 ) -> impl 'a + Iterator<Item = RemoteSelection> {
25394 let participant_names = collaboration_hub.user_names(cx);
25395 let participant_indices = collaboration_hub.user_participant_indices(cx);
25396 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
25397 let collaborators_by_replica_id = collaborators_by_peer_id
25398 .values()
25399 .map(|collaborator| (collaborator.replica_id, collaborator))
25400 .collect::<HashMap<_, _>>();
25401 self.buffer_snapshot()
25402 .selections_in_range(range, false)
25403 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
25404 if replica_id == ReplicaId::AGENT {
25405 Some(RemoteSelection {
25406 replica_id,
25407 selection,
25408 cursor_shape,
25409 line_mode,
25410 collaborator_id: CollaboratorId::Agent,
25411 user_name: Some("Agent".into()),
25412 color: cx.theme().players().agent(),
25413 })
25414 } else {
25415 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
25416 let participant_index = participant_indices.get(&collaborator.user_id).copied();
25417 let user_name = participant_names.get(&collaborator.user_id).cloned();
25418 Some(RemoteSelection {
25419 replica_id,
25420 selection,
25421 cursor_shape,
25422 line_mode,
25423 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
25424 user_name,
25425 color: if let Some(index) = participant_index {
25426 cx.theme().players().color_for_participant(index.0)
25427 } else {
25428 cx.theme().players().absent()
25429 },
25430 })
25431 }
25432 })
25433 }
25434
25435 pub fn hunks_for_ranges(
25436 &self,
25437 ranges: impl IntoIterator<Item = Range<Point>>,
25438 ) -> Vec<MultiBufferDiffHunk> {
25439 let mut hunks = Vec::new();
25440 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
25441 HashMap::default();
25442 for query_range in ranges {
25443 let query_rows =
25444 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
25445 for hunk in self.buffer_snapshot().diff_hunks_in_range(
25446 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
25447 ) {
25448 // Include deleted hunks that are adjacent to the query range, because
25449 // otherwise they would be missed.
25450 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
25451 if hunk.status().is_deleted() {
25452 intersects_range |= hunk.row_range.start == query_rows.end;
25453 intersects_range |= hunk.row_range.end == query_rows.start;
25454 }
25455 if intersects_range {
25456 if !processed_buffer_rows
25457 .entry(hunk.buffer_id)
25458 .or_default()
25459 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
25460 {
25461 continue;
25462 }
25463 hunks.push(hunk);
25464 }
25465 }
25466 }
25467
25468 hunks
25469 }
25470
25471 fn display_diff_hunks_for_rows<'a>(
25472 &'a self,
25473 display_rows: Range<DisplayRow>,
25474 folded_buffers: &'a HashSet<BufferId>,
25475 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
25476 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
25477 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
25478
25479 self.buffer_snapshot()
25480 .diff_hunks_in_range(buffer_start..buffer_end)
25481 .filter_map(|hunk| {
25482 if folded_buffers.contains(&hunk.buffer_id) {
25483 return None;
25484 }
25485
25486 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
25487 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
25488
25489 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
25490 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
25491
25492 let display_hunk = if hunk_display_start.column() != 0 {
25493 DisplayDiffHunk::Folded {
25494 display_row: hunk_display_start.row(),
25495 }
25496 } else {
25497 let mut end_row = hunk_display_end.row();
25498 if hunk_display_end.column() > 0 {
25499 end_row.0 += 1;
25500 }
25501 let is_created_file = hunk.is_created_file();
25502
25503 DisplayDiffHunk::Unfolded {
25504 status: hunk.status(),
25505 diff_base_byte_range: hunk.diff_base_byte_range.start.0
25506 ..hunk.diff_base_byte_range.end.0,
25507 word_diffs: hunk.word_diffs,
25508 display_row_range: hunk_display_start.row()..end_row,
25509 multi_buffer_range: Anchor::range_in_buffer(
25510 hunk.excerpt_id,
25511 hunk.buffer_range,
25512 ),
25513 is_created_file,
25514 }
25515 };
25516
25517 Some(display_hunk)
25518 })
25519 }
25520
25521 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
25522 self.display_snapshot
25523 .buffer_snapshot()
25524 .language_at(position)
25525 }
25526
25527 pub fn is_focused(&self) -> bool {
25528 self.is_focused
25529 }
25530
25531 pub fn placeholder_text(&self) -> Option<String> {
25532 self.placeholder_display_snapshot
25533 .as_ref()
25534 .map(|display_map| display_map.text())
25535 }
25536
25537 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
25538 self.scroll_anchor.scroll_position(&self.display_snapshot)
25539 }
25540
25541 pub fn gutter_dimensions(
25542 &self,
25543 font_id: FontId,
25544 font_size: Pixels,
25545 style: &EditorStyle,
25546 window: &mut Window,
25547 cx: &App,
25548 ) -> GutterDimensions {
25549 if self.show_gutter
25550 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
25551 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
25552 {
25553 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
25554 matches!(
25555 ProjectSettings::get_global(cx).git.git_gutter,
25556 GitGutterSetting::TrackedFiles
25557 )
25558 });
25559 let gutter_settings = EditorSettings::get_global(cx).gutter;
25560 let show_line_numbers = self
25561 .show_line_numbers
25562 .unwrap_or(gutter_settings.line_numbers);
25563 let line_gutter_width = if show_line_numbers {
25564 // Avoid flicker-like gutter resizes when the line number gains another digit by
25565 // only resizing the gutter on files with > 10**min_line_number_digits lines.
25566 let min_width_for_number_on_gutter =
25567 ch_advance * gutter_settings.min_line_number_digits as f32;
25568 self.max_line_number_width(style, window)
25569 .max(min_width_for_number_on_gutter)
25570 } else {
25571 0.0.into()
25572 };
25573
25574 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
25575 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
25576
25577 let git_blame_entries_width =
25578 self.git_blame_gutter_max_author_length
25579 .map(|max_author_length| {
25580 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
25581 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
25582
25583 /// The number of characters to dedicate to gaps and margins.
25584 const SPACING_WIDTH: usize = 4;
25585
25586 let max_char_count = max_author_length.min(renderer.max_author_length())
25587 + ::git::SHORT_SHA_LENGTH
25588 + MAX_RELATIVE_TIMESTAMP.len()
25589 + SPACING_WIDTH;
25590
25591 ch_advance * max_char_count
25592 });
25593
25594 let is_singleton = self.buffer_snapshot().is_singleton();
25595
25596 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
25597 left_padding += if !is_singleton {
25598 ch_width * 4.0
25599 } else if show_runnables || show_breakpoints {
25600 ch_width * 3.0
25601 } else if show_git_gutter && show_line_numbers {
25602 ch_width * 2.0
25603 } else if show_git_gutter || show_line_numbers {
25604 ch_width
25605 } else {
25606 px(0.)
25607 };
25608
25609 let shows_folds = is_singleton && gutter_settings.folds;
25610
25611 let right_padding = if shows_folds && show_line_numbers {
25612 ch_width * 4.0
25613 } else if shows_folds || (!is_singleton && show_line_numbers) {
25614 ch_width * 3.0
25615 } else if show_line_numbers {
25616 ch_width
25617 } else {
25618 px(0.)
25619 };
25620
25621 GutterDimensions {
25622 left_padding,
25623 right_padding,
25624 width: line_gutter_width + left_padding + right_padding,
25625 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
25626 git_blame_entries_width,
25627 }
25628 } else if self.offset_content {
25629 GutterDimensions::default_with_margin(font_id, font_size, cx)
25630 } else {
25631 GutterDimensions::default()
25632 }
25633 }
25634
25635 pub fn render_crease_toggle(
25636 &self,
25637 buffer_row: MultiBufferRow,
25638 row_contains_cursor: bool,
25639 editor: Entity<Editor>,
25640 window: &mut Window,
25641 cx: &mut App,
25642 ) -> Option<AnyElement> {
25643 let folded = self.is_line_folded(buffer_row);
25644 let mut is_foldable = false;
25645
25646 if let Some(crease) = self
25647 .crease_snapshot
25648 .query_row(buffer_row, self.buffer_snapshot())
25649 {
25650 is_foldable = true;
25651 match crease {
25652 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
25653 if let Some(render_toggle) = render_toggle {
25654 let toggle_callback =
25655 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
25656 if folded {
25657 editor.update(cx, |editor, cx| {
25658 editor.fold_at(buffer_row, window, cx)
25659 });
25660 } else {
25661 editor.update(cx, |editor, cx| {
25662 editor.unfold_at(buffer_row, window, cx)
25663 });
25664 }
25665 });
25666 return Some((render_toggle)(
25667 buffer_row,
25668 folded,
25669 toggle_callback,
25670 window,
25671 cx,
25672 ));
25673 }
25674 }
25675 }
25676 }
25677
25678 is_foldable |= self.starts_indent(buffer_row);
25679
25680 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25681 Some(
25682 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25683 .toggle_state(folded)
25684 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25685 if folded {
25686 this.unfold_at(buffer_row, window, cx);
25687 } else {
25688 this.fold_at(buffer_row, window, cx);
25689 }
25690 }))
25691 .into_any_element(),
25692 )
25693 } else {
25694 None
25695 }
25696 }
25697
25698 pub fn render_crease_trailer(
25699 &self,
25700 buffer_row: MultiBufferRow,
25701 window: &mut Window,
25702 cx: &mut App,
25703 ) -> Option<AnyElement> {
25704 let folded = self.is_line_folded(buffer_row);
25705 if let Crease::Inline { render_trailer, .. } = self
25706 .crease_snapshot
25707 .query_row(buffer_row, self.buffer_snapshot())?
25708 {
25709 let render_trailer = render_trailer.as_ref()?;
25710 Some(render_trailer(buffer_row, folded, window, cx))
25711 } else {
25712 None
25713 }
25714 }
25715
25716 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25717 let digit_count = self.widest_line_number().ilog10() + 1;
25718 column_pixels(style, digit_count as usize, window)
25719 }
25720
25721 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
25722 ///
25723 /// This is positive if `base` is before `line`.
25724 fn relative_line_delta(
25725 &self,
25726 current_selection_head: DisplayRow,
25727 first_visible_row: DisplayRow,
25728 consider_wrapped_lines: bool,
25729 ) -> i64 {
25730 let current_selection_head = current_selection_head.as_display_point().to_point(self);
25731 let first_visible_row = first_visible_row.as_display_point().to_point(self);
25732
25733 if consider_wrapped_lines {
25734 let wrap_snapshot = self.wrap_snapshot();
25735 let base_wrap_row = wrap_snapshot
25736 .make_wrap_point(current_selection_head, Bias::Left)
25737 .row();
25738 let wrap_row = wrap_snapshot
25739 .make_wrap_point(first_visible_row, Bias::Left)
25740 .row();
25741 wrap_row.0 as i64 - base_wrap_row.0 as i64
25742 } else {
25743 let folds = if current_selection_head < first_visible_row {
25744 self.folds_in_range(current_selection_head..first_visible_row)
25745 } else {
25746 self.folds_in_range(first_visible_row..current_selection_head)
25747 };
25748
25749 let folded_lines = folds
25750 .map(|fold| {
25751 let range = fold.range.0.to_point(self);
25752 range.end.row.saturating_sub(range.start.row)
25753 })
25754 .sum::<u32>() as i64;
25755
25756 first_visible_row.row as i64 - current_selection_head.row as i64 + folded_lines
25757 }
25758 }
25759
25760 /// Returns the unsigned relative line number to display for each row in `rows`.
25761 ///
25762 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
25763 pub fn calculate_relative_line_numbers(
25764 &self,
25765 rows: &Range<DisplayRow>,
25766 current_selection_head: DisplayRow,
25767 count_wrapped_lines: bool,
25768 ) -> HashMap<DisplayRow, u32> {
25769 let initial_offset =
25770 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
25771 let current_selection_point = current_selection_head.as_display_point().to_point(self);
25772
25773 self.row_infos(rows.start)
25774 .take(rows.len())
25775 .enumerate()
25776 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
25777 .filter(|(_row, row_info)| {
25778 row_info.buffer_row.is_some()
25779 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
25780 })
25781 .enumerate()
25782 .filter(|(_, (row, row_info))| {
25783 // We want to check here that
25784 // - the row is not the current selection head to ensure the current
25785 // line has absolute numbering
25786 // - similarly, should the selection head live in a soft-wrapped line
25787 // and we are not counting those, that the parent line keeps its
25788 // absolute number
25789 // - lastly, if we are in a deleted line, it is fine to number this
25790 // relative with 0, as otherwise it would have no line number at all
25791 (*row != current_selection_head
25792 && (count_wrapped_lines
25793 || row_info.buffer_row != Some(current_selection_point.row)))
25794 || row_info
25795 .diff_status
25796 .is_some_and(|status| status.is_deleted())
25797 })
25798 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
25799 .collect()
25800 }
25801}
25802
25803pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25804 let font_size = style.text.font_size.to_pixels(window.rem_size());
25805 let layout = window.text_system().shape_line(
25806 SharedString::from(" ".repeat(column)),
25807 font_size,
25808 &[TextRun {
25809 len: column,
25810 font: style.text.font(),
25811 color: Hsla::default(),
25812 ..Default::default()
25813 }],
25814 None,
25815 );
25816
25817 layout.width
25818}
25819
25820impl Deref for EditorSnapshot {
25821 type Target = DisplaySnapshot;
25822
25823 fn deref(&self) -> &Self::Target {
25824 &self.display_snapshot
25825 }
25826}
25827
25828#[derive(Clone, Debug, PartialEq, Eq)]
25829pub enum EditorEvent {
25830 InputIgnored {
25831 text: Arc<str>,
25832 },
25833 InputHandled {
25834 utf16_range_to_replace: Option<Range<isize>>,
25835 text: Arc<str>,
25836 },
25837 ExcerptsAdded {
25838 buffer: Entity<Buffer>,
25839 predecessor: ExcerptId,
25840 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25841 },
25842 ExcerptsRemoved {
25843 ids: Vec<ExcerptId>,
25844 removed_buffer_ids: Vec<BufferId>,
25845 },
25846 BufferFoldToggled {
25847 ids: Vec<ExcerptId>,
25848 folded: bool,
25849 },
25850 ExcerptsEdited {
25851 ids: Vec<ExcerptId>,
25852 },
25853 ExcerptsExpanded {
25854 ids: Vec<ExcerptId>,
25855 },
25856 ExpandExcerptsRequested {
25857 excerpt_ids: Vec<ExcerptId>,
25858 lines: u32,
25859 direction: ExpandExcerptDirection,
25860 },
25861 BufferEdited,
25862 Edited {
25863 transaction_id: clock::Lamport,
25864 },
25865 Reparsed(BufferId),
25866 Focused,
25867 FocusedIn,
25868 Blurred,
25869 DirtyChanged,
25870 Saved,
25871 TitleChanged,
25872 SelectionsChanged {
25873 local: bool,
25874 },
25875 ScrollPositionChanged {
25876 local: bool,
25877 autoscroll: bool,
25878 },
25879 TransactionUndone {
25880 transaction_id: clock::Lamport,
25881 },
25882 TransactionBegun {
25883 transaction_id: clock::Lamport,
25884 },
25885 CursorShapeChanged,
25886 BreadcrumbsChanged,
25887 PushedToNavHistory {
25888 anchor: Anchor,
25889 is_deactivate: bool,
25890 },
25891}
25892
25893impl EventEmitter<EditorEvent> for Editor {}
25894
25895impl Focusable for Editor {
25896 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25897 self.focus_handle.clone()
25898 }
25899}
25900
25901impl Render for Editor {
25902 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25903 EditorElement::new(&cx.entity(), self.create_style(cx))
25904 }
25905}
25906
25907impl EntityInputHandler for Editor {
25908 fn text_for_range(
25909 &mut self,
25910 range_utf16: Range<usize>,
25911 adjusted_range: &mut Option<Range<usize>>,
25912 _: &mut Window,
25913 cx: &mut Context<Self>,
25914 ) -> Option<String> {
25915 let snapshot = self.buffer.read(cx).read(cx);
25916 let start = snapshot.clip_offset_utf16(
25917 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25918 Bias::Left,
25919 );
25920 let end = snapshot.clip_offset_utf16(
25921 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25922 Bias::Right,
25923 );
25924 if (start.0.0..end.0.0) != range_utf16 {
25925 adjusted_range.replace(start.0.0..end.0.0);
25926 }
25927 Some(snapshot.text_for_range(start..end).collect())
25928 }
25929
25930 fn selected_text_range(
25931 &mut self,
25932 ignore_disabled_input: bool,
25933 _: &mut Window,
25934 cx: &mut Context<Self>,
25935 ) -> Option<UTF16Selection> {
25936 // Prevent the IME menu from appearing when holding down an alphabetic key
25937 // while input is disabled.
25938 if !ignore_disabled_input && !self.input_enabled {
25939 return None;
25940 }
25941
25942 let selection = self
25943 .selections
25944 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25945 let range = selection.range();
25946
25947 Some(UTF16Selection {
25948 range: range.start.0.0..range.end.0.0,
25949 reversed: selection.reversed,
25950 })
25951 }
25952
25953 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25954 let snapshot = self.buffer.read(cx).read(cx);
25955 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25956 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25957 }
25958
25959 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25960 self.clear_highlights::<InputComposition>(cx);
25961 self.ime_transaction.take();
25962 }
25963
25964 fn replace_text_in_range(
25965 &mut self,
25966 range_utf16: Option<Range<usize>>,
25967 text: &str,
25968 window: &mut Window,
25969 cx: &mut Context<Self>,
25970 ) {
25971 if !self.input_enabled {
25972 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25973 return;
25974 }
25975
25976 self.transact(window, cx, |this, window, cx| {
25977 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25978 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25979 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25980 Some(this.selection_replacement_ranges(range_utf16, cx))
25981 } else {
25982 this.marked_text_ranges(cx)
25983 };
25984
25985 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25986 let newest_selection_id = this.selections.newest_anchor().id;
25987 this.selections
25988 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25989 .iter()
25990 .zip(ranges_to_replace.iter())
25991 .find_map(|(selection, range)| {
25992 if selection.id == newest_selection_id {
25993 Some(
25994 (range.start.0.0 as isize - selection.head().0.0 as isize)
25995 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25996 )
25997 } else {
25998 None
25999 }
26000 })
26001 });
26002
26003 cx.emit(EditorEvent::InputHandled {
26004 utf16_range_to_replace: range_to_replace,
26005 text: text.into(),
26006 });
26007
26008 if let Some(new_selected_ranges) = new_selected_ranges {
26009 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
26010 selections.select_ranges(new_selected_ranges)
26011 });
26012 this.backspace(&Default::default(), window, cx);
26013 }
26014
26015 this.handle_input(text, window, cx);
26016 });
26017
26018 if let Some(transaction) = self.ime_transaction {
26019 self.buffer.update(cx, |buffer, cx| {
26020 buffer.group_until_transaction(transaction, cx);
26021 });
26022 }
26023
26024 self.unmark_text(window, cx);
26025 }
26026
26027 fn replace_and_mark_text_in_range(
26028 &mut self,
26029 range_utf16: Option<Range<usize>>,
26030 text: &str,
26031 new_selected_range_utf16: Option<Range<usize>>,
26032 window: &mut Window,
26033 cx: &mut Context<Self>,
26034 ) {
26035 if !self.input_enabled {
26036 return;
26037 }
26038
26039 let transaction = self.transact(window, cx, |this, window, cx| {
26040 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
26041 let snapshot = this.buffer.read(cx).read(cx);
26042 if let Some(relative_range_utf16) = range_utf16.as_ref() {
26043 for marked_range in &mut marked_ranges {
26044 marked_range.end = marked_range.start + relative_range_utf16.end;
26045 marked_range.start += relative_range_utf16.start;
26046 marked_range.start =
26047 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
26048 marked_range.end =
26049 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
26050 }
26051 }
26052 Some(marked_ranges)
26053 } else if let Some(range_utf16) = range_utf16 {
26054 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
26055 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
26056 Some(this.selection_replacement_ranges(range_utf16, cx))
26057 } else {
26058 None
26059 };
26060
26061 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
26062 let newest_selection_id = this.selections.newest_anchor().id;
26063 this.selections
26064 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
26065 .iter()
26066 .zip(ranges_to_replace.iter())
26067 .find_map(|(selection, range)| {
26068 if selection.id == newest_selection_id {
26069 Some(
26070 (range.start.0.0 as isize - selection.head().0.0 as isize)
26071 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
26072 )
26073 } else {
26074 None
26075 }
26076 })
26077 });
26078
26079 cx.emit(EditorEvent::InputHandled {
26080 utf16_range_to_replace: range_to_replace,
26081 text: text.into(),
26082 });
26083
26084 if let Some(ranges) = ranges_to_replace {
26085 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26086 s.select_ranges(ranges)
26087 });
26088 }
26089
26090 let marked_ranges = {
26091 let snapshot = this.buffer.read(cx).read(cx);
26092 this.selections
26093 .disjoint_anchors_arc()
26094 .iter()
26095 .map(|selection| {
26096 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
26097 })
26098 .collect::<Vec<_>>()
26099 };
26100
26101 if text.is_empty() {
26102 this.unmark_text(window, cx);
26103 } else {
26104 this.highlight_text::<InputComposition>(
26105 marked_ranges.clone(),
26106 HighlightStyle {
26107 underline: Some(UnderlineStyle {
26108 thickness: px(1.),
26109 color: None,
26110 wavy: false,
26111 }),
26112 ..Default::default()
26113 },
26114 cx,
26115 );
26116 }
26117
26118 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
26119 let use_autoclose = this.use_autoclose;
26120 let use_auto_surround = this.use_auto_surround;
26121 this.set_use_autoclose(false);
26122 this.set_use_auto_surround(false);
26123 this.handle_input(text, window, cx);
26124 this.set_use_autoclose(use_autoclose);
26125 this.set_use_auto_surround(use_auto_surround);
26126
26127 if let Some(new_selected_range) = new_selected_range_utf16 {
26128 let snapshot = this.buffer.read(cx).read(cx);
26129 let new_selected_ranges = marked_ranges
26130 .into_iter()
26131 .map(|marked_range| {
26132 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
26133 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
26134 insertion_start.0 + new_selected_range.start,
26135 ));
26136 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
26137 insertion_start.0 + new_selected_range.end,
26138 ));
26139 snapshot.clip_offset_utf16(new_start, Bias::Left)
26140 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
26141 })
26142 .collect::<Vec<_>>();
26143
26144 drop(snapshot);
26145 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
26146 selections.select_ranges(new_selected_ranges)
26147 });
26148 }
26149 });
26150
26151 self.ime_transaction = self.ime_transaction.or(transaction);
26152 if let Some(transaction) = self.ime_transaction {
26153 self.buffer.update(cx, |buffer, cx| {
26154 buffer.group_until_transaction(transaction, cx);
26155 });
26156 }
26157
26158 if self.text_highlights::<InputComposition>(cx).is_none() {
26159 self.ime_transaction.take();
26160 }
26161 }
26162
26163 fn bounds_for_range(
26164 &mut self,
26165 range_utf16: Range<usize>,
26166 element_bounds: gpui::Bounds<Pixels>,
26167 window: &mut Window,
26168 cx: &mut Context<Self>,
26169 ) -> Option<gpui::Bounds<Pixels>> {
26170 let text_layout_details = self.text_layout_details(window);
26171 let CharacterDimensions {
26172 em_width,
26173 em_advance,
26174 line_height,
26175 } = self.character_dimensions(window);
26176
26177 let snapshot = self.snapshot(window, cx);
26178 let scroll_position = snapshot.scroll_position();
26179 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
26180
26181 let start =
26182 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
26183 let x = Pixels::from(
26184 ScrollOffset::from(
26185 snapshot.x_for_display_point(start, &text_layout_details)
26186 + self.gutter_dimensions.full_width(),
26187 ) - scroll_left,
26188 );
26189 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
26190
26191 Some(Bounds {
26192 origin: element_bounds.origin + point(x, y),
26193 size: size(em_width, line_height),
26194 })
26195 }
26196
26197 fn character_index_for_point(
26198 &mut self,
26199 point: gpui::Point<Pixels>,
26200 _window: &mut Window,
26201 _cx: &mut Context<Self>,
26202 ) -> Option<usize> {
26203 let position_map = self.last_position_map.as_ref()?;
26204 if !position_map.text_hitbox.contains(&point) {
26205 return None;
26206 }
26207 let display_point = position_map.point_for_position(point).previous_valid;
26208 let anchor = position_map
26209 .snapshot
26210 .display_point_to_anchor(display_point, Bias::Left);
26211 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
26212 Some(utf16_offset.0.0)
26213 }
26214
26215 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
26216 self.input_enabled
26217 }
26218}
26219
26220trait SelectionExt {
26221 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
26222 fn spanned_rows(
26223 &self,
26224 include_end_if_at_line_start: bool,
26225 map: &DisplaySnapshot,
26226 ) -> Range<MultiBufferRow>;
26227}
26228
26229impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
26230 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
26231 let start = self
26232 .start
26233 .to_point(map.buffer_snapshot())
26234 .to_display_point(map);
26235 let end = self
26236 .end
26237 .to_point(map.buffer_snapshot())
26238 .to_display_point(map);
26239 if self.reversed {
26240 end..start
26241 } else {
26242 start..end
26243 }
26244 }
26245
26246 fn spanned_rows(
26247 &self,
26248 include_end_if_at_line_start: bool,
26249 map: &DisplaySnapshot,
26250 ) -> Range<MultiBufferRow> {
26251 let start = self.start.to_point(map.buffer_snapshot());
26252 let mut end = self.end.to_point(map.buffer_snapshot());
26253 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
26254 end.row -= 1;
26255 }
26256
26257 let buffer_start = map.prev_line_boundary(start).0;
26258 let buffer_end = map.next_line_boundary(end).0;
26259 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
26260 }
26261}
26262
26263impl<T: InvalidationRegion> InvalidationStack<T> {
26264 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
26265 where
26266 S: Clone + ToOffset,
26267 {
26268 while let Some(region) = self.last() {
26269 let all_selections_inside_invalidation_ranges =
26270 if selections.len() == region.ranges().len() {
26271 selections
26272 .iter()
26273 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
26274 .all(|(selection, invalidation_range)| {
26275 let head = selection.head().to_offset(buffer);
26276 invalidation_range.start <= head && invalidation_range.end >= head
26277 })
26278 } else {
26279 false
26280 };
26281
26282 if all_selections_inside_invalidation_ranges {
26283 break;
26284 } else {
26285 self.pop();
26286 }
26287 }
26288 }
26289}
26290
26291impl<T> Default for InvalidationStack<T> {
26292 fn default() -> Self {
26293 Self(Default::default())
26294 }
26295}
26296
26297impl<T> Deref for InvalidationStack<T> {
26298 type Target = Vec<T>;
26299
26300 fn deref(&self) -> &Self::Target {
26301 &self.0
26302 }
26303}
26304
26305impl<T> DerefMut for InvalidationStack<T> {
26306 fn deref_mut(&mut self) -> &mut Self::Target {
26307 &mut self.0
26308 }
26309}
26310
26311impl InvalidationRegion for SnippetState {
26312 fn ranges(&self) -> &[Range<Anchor>] {
26313 &self.ranges[self.active_index]
26314 }
26315}
26316
26317fn edit_prediction_edit_text(
26318 current_snapshot: &BufferSnapshot,
26319 edits: &[(Range<Anchor>, impl AsRef<str>)],
26320 edit_preview: &EditPreview,
26321 include_deletions: bool,
26322 cx: &App,
26323) -> HighlightedText {
26324 let edits = edits
26325 .iter()
26326 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
26327 .collect::<Vec<_>>();
26328
26329 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
26330}
26331
26332fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
26333 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
26334 // Just show the raw edit text with basic styling
26335 let mut text = String::new();
26336 let mut highlights = Vec::new();
26337
26338 let insertion_highlight_style = HighlightStyle {
26339 color: Some(cx.theme().colors().text),
26340 ..Default::default()
26341 };
26342
26343 for (_, edit_text) in edits {
26344 let start_offset = text.len();
26345 text.push_str(edit_text);
26346 let end_offset = text.len();
26347
26348 if start_offset < end_offset {
26349 highlights.push((start_offset..end_offset, insertion_highlight_style));
26350 }
26351 }
26352
26353 HighlightedText {
26354 text: text.into(),
26355 highlights,
26356 }
26357}
26358
26359pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
26360 match severity {
26361 lsp::DiagnosticSeverity::ERROR => colors.error,
26362 lsp::DiagnosticSeverity::WARNING => colors.warning,
26363 lsp::DiagnosticSeverity::INFORMATION => colors.info,
26364 lsp::DiagnosticSeverity::HINT => colors.info,
26365 _ => colors.ignored,
26366 }
26367}
26368
26369pub fn styled_runs_for_code_label<'a>(
26370 label: &'a CodeLabel,
26371 syntax_theme: &'a theme::SyntaxTheme,
26372 local_player: &'a theme::PlayerColor,
26373) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
26374 let fade_out = HighlightStyle {
26375 fade_out: Some(0.35),
26376 ..Default::default()
26377 };
26378
26379 let mut prev_end = label.filter_range.end;
26380 label
26381 .runs
26382 .iter()
26383 .enumerate()
26384 .flat_map(move |(ix, (range, highlight_id))| {
26385 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
26386 HighlightStyle {
26387 color: Some(local_player.cursor),
26388 ..Default::default()
26389 }
26390 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
26391 HighlightStyle {
26392 background_color: Some(local_player.selection),
26393 ..Default::default()
26394 }
26395 } else if let Some(style) = highlight_id.style(syntax_theme) {
26396 style
26397 } else {
26398 return Default::default();
26399 };
26400 let muted_style = style.highlight(fade_out);
26401
26402 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
26403 if range.start >= label.filter_range.end {
26404 if range.start > prev_end {
26405 runs.push((prev_end..range.start, fade_out));
26406 }
26407 runs.push((range.clone(), muted_style));
26408 } else if range.end <= label.filter_range.end {
26409 runs.push((range.clone(), style));
26410 } else {
26411 runs.push((range.start..label.filter_range.end, style));
26412 runs.push((label.filter_range.end..range.end, muted_style));
26413 }
26414 prev_end = cmp::max(prev_end, range.end);
26415
26416 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
26417 runs.push((prev_end..label.text.len(), fade_out));
26418 }
26419
26420 runs
26421 })
26422}
26423
26424pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
26425 let mut prev_index = 0;
26426 let mut prev_codepoint: Option<char> = None;
26427 text.char_indices()
26428 .chain([(text.len(), '\0')])
26429 .filter_map(move |(index, codepoint)| {
26430 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26431 let is_boundary = index == text.len()
26432 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
26433 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
26434 if is_boundary {
26435 let chunk = &text[prev_index..index];
26436 prev_index = index;
26437 Some(chunk)
26438 } else {
26439 None
26440 }
26441 })
26442}
26443
26444/// Given a string of text immediately before the cursor, iterates over possible
26445/// strings a snippet could match to. More precisely: returns an iterator over
26446/// suffixes of `text` created by splitting at word boundaries (before & after
26447/// every non-word character).
26448///
26449/// Shorter suffixes are returned first.
26450pub(crate) fn snippet_candidate_suffixes(
26451 text: &str,
26452 is_word_char: impl Fn(char) -> bool,
26453) -> impl std::iter::Iterator<Item = &str> {
26454 let mut prev_index = text.len();
26455 let mut prev_codepoint = None;
26456 text.char_indices()
26457 .rev()
26458 .chain([(0, '\0')])
26459 .filter_map(move |(index, codepoint)| {
26460 let prev_index = std::mem::replace(&mut prev_index, index);
26461 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26462 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
26463 None
26464 } else {
26465 let chunk = &text[prev_index..]; // go to end of string
26466 Some(chunk)
26467 }
26468 })
26469}
26470
26471pub trait RangeToAnchorExt: Sized {
26472 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
26473
26474 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
26475 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
26476 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
26477 }
26478}
26479
26480impl<T: ToOffset> RangeToAnchorExt for Range<T> {
26481 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
26482 let start_offset = self.start.to_offset(snapshot);
26483 let end_offset = self.end.to_offset(snapshot);
26484 if start_offset == end_offset {
26485 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
26486 } else {
26487 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
26488 }
26489 }
26490}
26491
26492pub trait RowExt {
26493 fn as_f64(&self) -> f64;
26494
26495 fn next_row(&self) -> Self;
26496
26497 fn previous_row(&self) -> Self;
26498
26499 fn minus(&self, other: Self) -> u32;
26500}
26501
26502impl RowExt for DisplayRow {
26503 fn as_f64(&self) -> f64 {
26504 self.0 as _
26505 }
26506
26507 fn next_row(&self) -> Self {
26508 Self(self.0 + 1)
26509 }
26510
26511 fn previous_row(&self) -> Self {
26512 Self(self.0.saturating_sub(1))
26513 }
26514
26515 fn minus(&self, other: Self) -> u32 {
26516 self.0 - other.0
26517 }
26518}
26519
26520impl RowExt for MultiBufferRow {
26521 fn as_f64(&self) -> f64 {
26522 self.0 as _
26523 }
26524
26525 fn next_row(&self) -> Self {
26526 Self(self.0 + 1)
26527 }
26528
26529 fn previous_row(&self) -> Self {
26530 Self(self.0.saturating_sub(1))
26531 }
26532
26533 fn minus(&self, other: Self) -> u32 {
26534 self.0 - other.0
26535 }
26536}
26537
26538trait RowRangeExt {
26539 type Row;
26540
26541 fn len(&self) -> usize;
26542
26543 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
26544}
26545
26546impl RowRangeExt for Range<MultiBufferRow> {
26547 type Row = MultiBufferRow;
26548
26549 fn len(&self) -> usize {
26550 (self.end.0 - self.start.0) as usize
26551 }
26552
26553 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
26554 (self.start.0..self.end.0).map(MultiBufferRow)
26555 }
26556}
26557
26558impl RowRangeExt for Range<DisplayRow> {
26559 type Row = DisplayRow;
26560
26561 fn len(&self) -> usize {
26562 (self.end.0 - self.start.0) as usize
26563 }
26564
26565 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
26566 (self.start.0..self.end.0).map(DisplayRow)
26567 }
26568}
26569
26570/// If select range has more than one line, we
26571/// just point the cursor to range.start.
26572fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
26573 if range.start.row == range.end.row {
26574 range
26575 } else {
26576 range.start..range.start
26577 }
26578}
26579pub struct KillRing(ClipboardItem);
26580impl Global for KillRing {}
26581
26582const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
26583
26584enum BreakpointPromptEditAction {
26585 Log,
26586 Condition,
26587 HitCondition,
26588}
26589
26590struct BreakpointPromptEditor {
26591 pub(crate) prompt: Entity<Editor>,
26592 editor: WeakEntity<Editor>,
26593 breakpoint_anchor: Anchor,
26594 breakpoint: Breakpoint,
26595 edit_action: BreakpointPromptEditAction,
26596 block_ids: HashSet<CustomBlockId>,
26597 editor_margins: Arc<Mutex<EditorMargins>>,
26598 _subscriptions: Vec<Subscription>,
26599}
26600
26601impl BreakpointPromptEditor {
26602 const MAX_LINES: u8 = 4;
26603
26604 fn new(
26605 editor: WeakEntity<Editor>,
26606 breakpoint_anchor: Anchor,
26607 breakpoint: Breakpoint,
26608 edit_action: BreakpointPromptEditAction,
26609 window: &mut Window,
26610 cx: &mut Context<Self>,
26611 ) -> Self {
26612 let base_text = match edit_action {
26613 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
26614 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
26615 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
26616 }
26617 .map(|msg| msg.to_string())
26618 .unwrap_or_default();
26619
26620 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
26621 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26622
26623 let prompt = cx.new(|cx| {
26624 let mut prompt = Editor::new(
26625 EditorMode::AutoHeight {
26626 min_lines: 1,
26627 max_lines: Some(Self::MAX_LINES as usize),
26628 },
26629 buffer,
26630 None,
26631 window,
26632 cx,
26633 );
26634 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
26635 prompt.set_show_cursor_when_unfocused(false, cx);
26636 prompt.set_placeholder_text(
26637 match edit_action {
26638 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
26639 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
26640 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
26641 },
26642 window,
26643 cx,
26644 );
26645
26646 prompt
26647 });
26648
26649 Self {
26650 prompt,
26651 editor,
26652 breakpoint_anchor,
26653 breakpoint,
26654 edit_action,
26655 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
26656 block_ids: Default::default(),
26657 _subscriptions: vec![],
26658 }
26659 }
26660
26661 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
26662 self.block_ids.extend(block_ids)
26663 }
26664
26665 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
26666 if let Some(editor) = self.editor.upgrade() {
26667 let message = self
26668 .prompt
26669 .read(cx)
26670 .buffer
26671 .read(cx)
26672 .as_singleton()
26673 .expect("A multi buffer in breakpoint prompt isn't possible")
26674 .read(cx)
26675 .as_rope()
26676 .to_string();
26677
26678 editor.update(cx, |editor, cx| {
26679 editor.edit_breakpoint_at_anchor(
26680 self.breakpoint_anchor,
26681 self.breakpoint.clone(),
26682 match self.edit_action {
26683 BreakpointPromptEditAction::Log => {
26684 BreakpointEditAction::EditLogMessage(message.into())
26685 }
26686 BreakpointPromptEditAction::Condition => {
26687 BreakpointEditAction::EditCondition(message.into())
26688 }
26689 BreakpointPromptEditAction::HitCondition => {
26690 BreakpointEditAction::EditHitCondition(message.into())
26691 }
26692 },
26693 cx,
26694 );
26695
26696 editor.remove_blocks(self.block_ids.clone(), None, cx);
26697 cx.focus_self(window);
26698 });
26699 }
26700 }
26701
26702 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
26703 self.editor
26704 .update(cx, |editor, cx| {
26705 editor.remove_blocks(self.block_ids.clone(), None, cx);
26706 window.focus(&editor.focus_handle, cx);
26707 })
26708 .log_err();
26709 }
26710
26711 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
26712 let settings = ThemeSettings::get_global(cx);
26713 let text_style = TextStyle {
26714 color: if self.prompt.read(cx).read_only(cx) {
26715 cx.theme().colors().text_disabled
26716 } else {
26717 cx.theme().colors().text
26718 },
26719 font_family: settings.buffer_font.family.clone(),
26720 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26721 font_size: settings.buffer_font_size(cx).into(),
26722 font_weight: settings.buffer_font.weight,
26723 line_height: relative(settings.buffer_line_height.value()),
26724 ..Default::default()
26725 };
26726 EditorElement::new(
26727 &self.prompt,
26728 EditorStyle {
26729 background: cx.theme().colors().editor_background,
26730 local_player: cx.theme().players().local(),
26731 text: text_style,
26732 ..Default::default()
26733 },
26734 )
26735 }
26736}
26737
26738impl Render for BreakpointPromptEditor {
26739 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26740 let editor_margins = *self.editor_margins.lock();
26741 let gutter_dimensions = editor_margins.gutter;
26742 h_flex()
26743 .key_context("Editor")
26744 .bg(cx.theme().colors().editor_background)
26745 .border_y_1()
26746 .border_color(cx.theme().status().info_border)
26747 .size_full()
26748 .py(window.line_height() / 2.5)
26749 .on_action(cx.listener(Self::confirm))
26750 .on_action(cx.listener(Self::cancel))
26751 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
26752 .child(div().flex_1().child(self.render_prompt_editor(cx)))
26753 }
26754}
26755
26756impl Focusable for BreakpointPromptEditor {
26757 fn focus_handle(&self, cx: &App) -> FocusHandle {
26758 self.prompt.focus_handle(cx)
26759 }
26760}
26761
26762fn all_edits_insertions_or_deletions(
26763 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26764 snapshot: &MultiBufferSnapshot,
26765) -> bool {
26766 let mut all_insertions = true;
26767 let mut all_deletions = true;
26768
26769 for (range, new_text) in edits.iter() {
26770 let range_is_empty = range.to_offset(snapshot).is_empty();
26771 let text_is_empty = new_text.is_empty();
26772
26773 if range_is_empty != text_is_empty {
26774 if range_is_empty {
26775 all_deletions = false;
26776 } else {
26777 all_insertions = false;
26778 }
26779 } else {
26780 return false;
26781 }
26782
26783 if !all_insertions && !all_deletions {
26784 return false;
26785 }
26786 }
26787 all_insertions || all_deletions
26788}
26789
26790struct MissingEditPredictionKeybindingTooltip;
26791
26792impl Render for MissingEditPredictionKeybindingTooltip {
26793 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26794 ui::tooltip_container(cx, |container, cx| {
26795 container
26796 .flex_shrink_0()
26797 .max_w_80()
26798 .min_h(rems_from_px(124.))
26799 .justify_between()
26800 .child(
26801 v_flex()
26802 .flex_1()
26803 .text_ui_sm(cx)
26804 .child(Label::new("Conflict with Accept Keybinding"))
26805 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26806 )
26807 .child(
26808 h_flex()
26809 .pb_1()
26810 .gap_1()
26811 .items_end()
26812 .w_full()
26813 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26814 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26815 }))
26816 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26817 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26818 })),
26819 )
26820 })
26821 }
26822}
26823
26824#[derive(Debug, Clone, Copy, PartialEq)]
26825pub struct LineHighlight {
26826 pub background: Background,
26827 pub border: Option<gpui::Hsla>,
26828 pub include_gutter: bool,
26829 pub type_id: Option<TypeId>,
26830}
26831
26832struct LineManipulationResult {
26833 pub new_text: String,
26834 pub line_count_before: usize,
26835 pub line_count_after: usize,
26836}
26837
26838fn render_diff_hunk_controls(
26839 row: u32,
26840 status: &DiffHunkStatus,
26841 hunk_range: Range<Anchor>,
26842 is_created_file: bool,
26843 line_height: Pixels,
26844 editor: &Entity<Editor>,
26845 _window: &mut Window,
26846 cx: &mut App,
26847) -> AnyElement {
26848 h_flex()
26849 .h(line_height)
26850 .mr_1()
26851 .gap_1()
26852 .px_0p5()
26853 .pb_1()
26854 .border_x_1()
26855 .border_b_1()
26856 .border_color(cx.theme().colors().border_variant)
26857 .rounded_b_lg()
26858 .bg(cx.theme().colors().editor_background)
26859 .gap_1()
26860 .block_mouse_except_scroll()
26861 .shadow_md()
26862 .child(if status.has_secondary_hunk() {
26863 Button::new(("stage", row as u64), "Stage")
26864 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26865 .tooltip({
26866 let focus_handle = editor.focus_handle(cx);
26867 move |_window, cx| {
26868 Tooltip::for_action_in(
26869 "Stage Hunk",
26870 &::git::ToggleStaged,
26871 &focus_handle,
26872 cx,
26873 )
26874 }
26875 })
26876 .on_click({
26877 let editor = editor.clone();
26878 move |_event, _window, cx| {
26879 editor.update(cx, |editor, cx| {
26880 editor.stage_or_unstage_diff_hunks(
26881 true,
26882 vec![hunk_range.start..hunk_range.start],
26883 cx,
26884 );
26885 });
26886 }
26887 })
26888 } else {
26889 Button::new(("unstage", row as u64), "Unstage")
26890 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26891 .tooltip({
26892 let focus_handle = editor.focus_handle(cx);
26893 move |_window, cx| {
26894 Tooltip::for_action_in(
26895 "Unstage Hunk",
26896 &::git::ToggleStaged,
26897 &focus_handle,
26898 cx,
26899 )
26900 }
26901 })
26902 .on_click({
26903 let editor = editor.clone();
26904 move |_event, _window, cx| {
26905 editor.update(cx, |editor, cx| {
26906 editor.stage_or_unstage_diff_hunks(
26907 false,
26908 vec![hunk_range.start..hunk_range.start],
26909 cx,
26910 );
26911 });
26912 }
26913 })
26914 })
26915 .child(
26916 Button::new(("restore", row as u64), "Restore")
26917 .tooltip({
26918 let focus_handle = editor.focus_handle(cx);
26919 move |_window, cx| {
26920 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26921 }
26922 })
26923 .on_click({
26924 let editor = editor.clone();
26925 move |_event, window, cx| {
26926 editor.update(cx, |editor, cx| {
26927 let snapshot = editor.snapshot(window, cx);
26928 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26929 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26930 });
26931 }
26932 })
26933 .disabled(is_created_file),
26934 )
26935 .when(
26936 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26937 |el| {
26938 el.child(
26939 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26940 .shape(IconButtonShape::Square)
26941 .icon_size(IconSize::Small)
26942 // .disabled(!has_multiple_hunks)
26943 .tooltip({
26944 let focus_handle = editor.focus_handle(cx);
26945 move |_window, cx| {
26946 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26947 }
26948 })
26949 .on_click({
26950 let editor = editor.clone();
26951 move |_event, window, cx| {
26952 editor.update(cx, |editor, cx| {
26953 let snapshot = editor.snapshot(window, cx);
26954 let position =
26955 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26956 editor.go_to_hunk_before_or_after_position(
26957 &snapshot,
26958 position,
26959 Direction::Next,
26960 window,
26961 cx,
26962 );
26963 editor.expand_selected_diff_hunks(cx);
26964 });
26965 }
26966 }),
26967 )
26968 .child(
26969 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26970 .shape(IconButtonShape::Square)
26971 .icon_size(IconSize::Small)
26972 // .disabled(!has_multiple_hunks)
26973 .tooltip({
26974 let focus_handle = editor.focus_handle(cx);
26975 move |_window, cx| {
26976 Tooltip::for_action_in(
26977 "Previous Hunk",
26978 &GoToPreviousHunk,
26979 &focus_handle,
26980 cx,
26981 )
26982 }
26983 })
26984 .on_click({
26985 let editor = editor.clone();
26986 move |_event, window, cx| {
26987 editor.update(cx, |editor, cx| {
26988 let snapshot = editor.snapshot(window, cx);
26989 let point =
26990 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26991 editor.go_to_hunk_before_or_after_position(
26992 &snapshot,
26993 point,
26994 Direction::Prev,
26995 window,
26996 cx,
26997 );
26998 editor.expand_selected_diff_hunks(cx);
26999 });
27000 }
27001 }),
27002 )
27003 },
27004 )
27005 .into_any_element()
27006}
27007
27008pub fn multibuffer_context_lines(cx: &App) -> u32 {
27009 EditorSettings::try_get(cx)
27010 .map(|settings| settings.excerpt_context_lines)
27011 .unwrap_or(2)
27012 .min(32)
27013}