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().map(|_| range.start.row..=range.end.row);
1648
1649 Self {
1650 len,
1651 is_entire_line,
1652 first_line_indent,
1653 file_path,
1654 line_range,
1655 }
1656 }
1657}
1658
1659// selections, scroll behavior, was newest selection reversed
1660type SelectSyntaxNodeHistoryState = (
1661 Box<[Selection<MultiBufferOffset>]>,
1662 SelectSyntaxNodeScrollBehavior,
1663 bool,
1664);
1665
1666#[derive(Default)]
1667struct SelectSyntaxNodeHistory {
1668 stack: Vec<SelectSyntaxNodeHistoryState>,
1669 // disable temporarily to allow changing selections without losing the stack
1670 pub disable_clearing: bool,
1671}
1672
1673impl SelectSyntaxNodeHistory {
1674 pub fn try_clear(&mut self) {
1675 if !self.disable_clearing {
1676 self.stack.clear();
1677 }
1678 }
1679
1680 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1681 self.stack.push(selection);
1682 }
1683
1684 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1685 self.stack.pop()
1686 }
1687}
1688
1689enum SelectSyntaxNodeScrollBehavior {
1690 CursorTop,
1691 FitSelection,
1692 CursorBottom,
1693}
1694
1695#[derive(Debug)]
1696pub(crate) struct NavigationData {
1697 cursor_anchor: Anchor,
1698 cursor_position: Point,
1699 scroll_anchor: ScrollAnchor,
1700 scroll_top_row: u32,
1701}
1702
1703#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1704pub enum GotoDefinitionKind {
1705 Symbol,
1706 Declaration,
1707 Type,
1708 Implementation,
1709}
1710
1711pub enum FormatTarget {
1712 Buffers(HashSet<Entity<Buffer>>),
1713 Ranges(Vec<Range<MultiBufferPoint>>),
1714}
1715
1716pub(crate) struct FocusedBlock {
1717 id: BlockId,
1718 focus_handle: WeakFocusHandle,
1719}
1720
1721#[derive(Clone, Debug)]
1722enum JumpData {
1723 MultiBufferRow {
1724 row: MultiBufferRow,
1725 line_offset_from_top: u32,
1726 },
1727 MultiBufferPoint {
1728 excerpt_id: ExcerptId,
1729 position: Point,
1730 anchor: text::Anchor,
1731 line_offset_from_top: u32,
1732 },
1733}
1734
1735pub enum MultibufferSelectionMode {
1736 First,
1737 All,
1738}
1739
1740#[derive(Clone, Copy, Debug, Default)]
1741pub struct RewrapOptions {
1742 pub override_language_settings: bool,
1743 pub preserve_existing_whitespace: bool,
1744}
1745
1746impl Editor {
1747 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1748 let buffer = cx.new(|cx| Buffer::local("", cx));
1749 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1750 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1751 }
1752
1753 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1754 let buffer = cx.new(|cx| Buffer::local("", cx));
1755 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1756 Self::new(EditorMode::full(), buffer, None, window, cx)
1757 }
1758
1759 pub fn auto_height(
1760 min_lines: usize,
1761 max_lines: usize,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 let buffer = cx.new(|cx| Buffer::local("", cx));
1766 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1767 Self::new(
1768 EditorMode::AutoHeight {
1769 min_lines,
1770 max_lines: Some(max_lines),
1771 },
1772 buffer,
1773 None,
1774 window,
1775 cx,
1776 )
1777 }
1778
1779 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1780 /// The editor grows as tall as needed to fit its content.
1781 pub fn auto_height_unbounded(
1782 min_lines: usize,
1783 window: &mut Window,
1784 cx: &mut Context<Self>,
1785 ) -> Self {
1786 let buffer = cx.new(|cx| Buffer::local("", cx));
1787 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1788 Self::new(
1789 EditorMode::AutoHeight {
1790 min_lines,
1791 max_lines: None,
1792 },
1793 buffer,
1794 None,
1795 window,
1796 cx,
1797 )
1798 }
1799
1800 pub fn for_buffer(
1801 buffer: Entity<Buffer>,
1802 project: Option<Entity<Project>>,
1803 window: &mut Window,
1804 cx: &mut Context<Self>,
1805 ) -> Self {
1806 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1807 Self::new(EditorMode::full(), buffer, project, window, cx)
1808 }
1809
1810 pub fn for_multibuffer(
1811 buffer: Entity<MultiBuffer>,
1812 project: Option<Entity<Project>>,
1813 window: &mut Window,
1814 cx: &mut Context<Self>,
1815 ) -> Self {
1816 Self::new(EditorMode::full(), buffer, project, window, cx)
1817 }
1818
1819 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1820 let mut clone = Self::new(
1821 self.mode.clone(),
1822 self.buffer.clone(),
1823 self.project.clone(),
1824 window,
1825 cx,
1826 );
1827 self.display_map.update(cx, |display_map, cx| {
1828 let snapshot = display_map.snapshot(cx);
1829 clone.display_map.update(cx, |display_map, cx| {
1830 display_map.set_state(&snapshot, cx);
1831 });
1832 });
1833 clone.folds_did_change(cx);
1834 clone.selections.clone_state(&self.selections);
1835 clone.scroll_manager.clone_state(&self.scroll_manager);
1836 clone.searchable = self.searchable;
1837 clone.read_only = self.read_only;
1838 clone
1839 }
1840
1841 pub fn new(
1842 mode: EditorMode,
1843 buffer: Entity<MultiBuffer>,
1844 project: Option<Entity<Project>>,
1845 window: &mut Window,
1846 cx: &mut Context<Self>,
1847 ) -> Self {
1848 Editor::new_internal(mode, buffer, project, None, window, cx)
1849 }
1850
1851 pub fn sticky_headers(
1852 &self,
1853 style: &EditorStyle,
1854 cx: &App,
1855 ) -> Option<Vec<OutlineItem<Anchor>>> {
1856 let multi_buffer = self.buffer().read(cx);
1857 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1858 let multi_buffer_visible_start = self
1859 .scroll_manager
1860 .anchor()
1861 .anchor
1862 .to_point(&multi_buffer_snapshot);
1863 let max_row = multi_buffer_snapshot.max_point().row;
1864
1865 let start_row = (multi_buffer_visible_start.row).min(max_row);
1866 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1867
1868 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1869 let outline_items = buffer
1870 .outline_items_containing(
1871 Point::new(start_row, 0)..Point::new(end_row, 0),
1872 true,
1873 Some(style.syntax.as_ref()),
1874 )
1875 .into_iter()
1876 .map(|outline_item| OutlineItem {
1877 depth: outline_item.depth,
1878 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1879 source_range_for_text: Anchor::range_in_buffer(
1880 *excerpt_id,
1881 outline_item.source_range_for_text,
1882 ),
1883 text: outline_item.text,
1884 highlight_ranges: outline_item.highlight_ranges,
1885 name_ranges: outline_item.name_ranges,
1886 body_range: outline_item
1887 .body_range
1888 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1889 annotation_range: outline_item
1890 .annotation_range
1891 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1892 });
1893 return Some(outline_items.collect());
1894 }
1895
1896 None
1897 }
1898
1899 fn new_internal(
1900 mode: EditorMode,
1901 multi_buffer: Entity<MultiBuffer>,
1902 project: Option<Entity<Project>>,
1903 display_map: Option<Entity<DisplayMap>>,
1904 window: &mut Window,
1905 cx: &mut Context<Self>,
1906 ) -> Self {
1907 debug_assert!(
1908 display_map.is_none() || mode.is_minimap(),
1909 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1910 );
1911
1912 let full_mode = mode.is_full();
1913 let is_minimap = mode.is_minimap();
1914 let diagnostics_max_severity = if full_mode {
1915 EditorSettings::get_global(cx)
1916 .diagnostics_max_severity
1917 .unwrap_or(DiagnosticSeverity::Hint)
1918 } else {
1919 DiagnosticSeverity::Off
1920 };
1921 let style = window.text_style();
1922 let font_size = style.font_size.to_pixels(window.rem_size());
1923 let editor = cx.entity().downgrade();
1924 let fold_placeholder = FoldPlaceholder {
1925 constrain_width: false,
1926 render: Arc::new(move |fold_id, fold_range, cx| {
1927 let editor = editor.clone();
1928 div()
1929 .id(fold_id)
1930 .bg(cx.theme().colors().ghost_element_background)
1931 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1932 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1933 .rounded_xs()
1934 .size_full()
1935 .cursor_pointer()
1936 .child("⋯")
1937 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1938 .on_click(move |_, _window, cx| {
1939 editor
1940 .update(cx, |editor, cx| {
1941 editor.unfold_ranges(
1942 &[fold_range.start..fold_range.end],
1943 true,
1944 false,
1945 cx,
1946 );
1947 cx.stop_propagation();
1948 })
1949 .ok();
1950 })
1951 .into_any()
1952 }),
1953 merge_adjacent: true,
1954 ..FoldPlaceholder::default()
1955 };
1956 let display_map = display_map.unwrap_or_else(|| {
1957 cx.new(|cx| {
1958 DisplayMap::new(
1959 multi_buffer.clone(),
1960 style.font(),
1961 font_size,
1962 None,
1963 FILE_HEADER_HEIGHT,
1964 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1965 fold_placeholder,
1966 diagnostics_max_severity,
1967 cx,
1968 )
1969 })
1970 });
1971
1972 let selections = SelectionsCollection::new();
1973
1974 let blink_manager = cx.new(|cx| {
1975 let mut blink_manager = BlinkManager::new(
1976 CURSOR_BLINK_INTERVAL,
1977 |cx| EditorSettings::get_global(cx).cursor_blink,
1978 cx,
1979 );
1980 if is_minimap {
1981 blink_manager.disable(cx);
1982 }
1983 blink_manager
1984 });
1985
1986 let soft_wrap_mode_override =
1987 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1988
1989 let mut project_subscriptions = Vec::new();
1990 if full_mode && let Some(project) = project.as_ref() {
1991 project_subscriptions.push(cx.subscribe_in(
1992 project,
1993 window,
1994 |editor, _, event, window, cx| match event {
1995 project::Event::RefreshCodeLens => {
1996 // we always query lens with actions, without storing them, always refreshing them
1997 }
1998 project::Event::RefreshInlayHints {
1999 server_id,
2000 request_id,
2001 } => {
2002 editor.refresh_inlay_hints(
2003 InlayHintRefreshReason::RefreshRequested {
2004 server_id: *server_id,
2005 request_id: *request_id,
2006 },
2007 cx,
2008 );
2009 }
2010 project::Event::LanguageServerRemoved(..) => {
2011 if editor.tasks_update_task.is_none() {
2012 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2013 }
2014 editor.registered_buffers.clear();
2015 editor.register_visible_buffers(cx);
2016 }
2017 project::Event::LanguageServerAdded(..) => {
2018 if editor.tasks_update_task.is_none() {
2019 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2020 }
2021 }
2022 project::Event::SnippetEdit(id, snippet_edits) => {
2023 // todo(lw): Non singletons
2024 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2025 let snapshot = buffer.read(cx).snapshot();
2026 let focus_handle = editor.focus_handle(cx);
2027 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2028 for (range, snippet) in snippet_edits {
2029 let buffer_range =
2030 language::range_from_lsp(*range).to_offset(&snapshot);
2031 editor
2032 .insert_snippet(
2033 &[MultiBufferOffset(buffer_range.start)
2034 ..MultiBufferOffset(buffer_range.end)],
2035 snippet.clone(),
2036 window,
2037 cx,
2038 )
2039 .ok();
2040 }
2041 }
2042 }
2043 }
2044 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2045 let buffer_id = *buffer_id;
2046 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2047 editor.register_buffer(buffer_id, cx);
2048 editor.update_lsp_data(Some(buffer_id), window, cx);
2049 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2050 refresh_linked_ranges(editor, window, cx);
2051 editor.refresh_code_actions(window, cx);
2052 editor.refresh_document_highlights(cx);
2053 }
2054 }
2055
2056 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2057 let Some(workspace) = editor.workspace() else {
2058 return;
2059 };
2060 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2061 else {
2062 return;
2063 };
2064
2065 if active_editor.entity_id() == cx.entity_id() {
2066 let entity_id = cx.entity_id();
2067 workspace.update(cx, |this, cx| {
2068 this.panes_mut()
2069 .iter_mut()
2070 .filter(|pane| pane.entity_id() != entity_id)
2071 .for_each(|p| {
2072 p.update(cx, |pane, _| {
2073 pane.nav_history_mut().rename_item(
2074 entity_id,
2075 project_path.clone(),
2076 abs_path.clone().into(),
2077 );
2078 })
2079 });
2080 });
2081
2082 Self::open_transaction_for_hidden_buffers(
2083 workspace,
2084 transaction.clone(),
2085 "Rename".to_string(),
2086 window,
2087 cx,
2088 );
2089 }
2090 }
2091
2092 project::Event::WorkspaceEditApplied(transaction) => {
2093 let Some(workspace) = editor.workspace() else {
2094 return;
2095 };
2096 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2097 else {
2098 return;
2099 };
2100
2101 if active_editor.entity_id() == cx.entity_id() {
2102 Self::open_transaction_for_hidden_buffers(
2103 workspace,
2104 transaction.clone(),
2105 "LSP Edit".to_string(),
2106 window,
2107 cx,
2108 );
2109 }
2110 }
2111
2112 _ => {}
2113 },
2114 ));
2115 if let Some(task_inventory) = project
2116 .read(cx)
2117 .task_store()
2118 .read(cx)
2119 .task_inventory()
2120 .cloned()
2121 {
2122 project_subscriptions.push(cx.observe_in(
2123 &task_inventory,
2124 window,
2125 |editor, _, window, cx| {
2126 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2127 },
2128 ));
2129 };
2130
2131 project_subscriptions.push(cx.subscribe_in(
2132 &project.read(cx).breakpoint_store(),
2133 window,
2134 |editor, _, event, window, cx| match event {
2135 BreakpointStoreEvent::ClearDebugLines => {
2136 editor.clear_row_highlights::<ActiveDebugLine>();
2137 editor.refresh_inline_values(cx);
2138 }
2139 BreakpointStoreEvent::SetDebugLine => {
2140 if editor.go_to_active_debug_line(window, cx) {
2141 cx.stop_propagation();
2142 }
2143
2144 editor.refresh_inline_values(cx);
2145 }
2146 _ => {}
2147 },
2148 ));
2149 let git_store = project.read(cx).git_store().clone();
2150 let project = project.clone();
2151 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2152 if let GitStoreEvent::RepositoryAdded = event {
2153 this.load_diff_task = Some(
2154 update_uncommitted_diff_for_buffer(
2155 cx.entity(),
2156 &project,
2157 this.buffer.read(cx).all_buffers(),
2158 this.buffer.clone(),
2159 cx,
2160 )
2161 .shared(),
2162 );
2163 }
2164 }));
2165 }
2166
2167 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2168
2169 let inlay_hint_settings =
2170 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2171 let focus_handle = cx.focus_handle();
2172 if !is_minimap {
2173 cx.on_focus(&focus_handle, window, Self::handle_focus)
2174 .detach();
2175 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2176 .detach();
2177 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2178 .detach();
2179 cx.on_blur(&focus_handle, window, Self::handle_blur)
2180 .detach();
2181 cx.observe_pending_input(window, Self::observe_pending_input)
2182 .detach();
2183 }
2184
2185 let show_indent_guides =
2186 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2187 Some(false)
2188 } else {
2189 None
2190 };
2191
2192 let breakpoint_store = match (&mode, project.as_ref()) {
2193 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2194 _ => None,
2195 };
2196
2197 let mut code_action_providers = Vec::new();
2198 let mut load_uncommitted_diff = None;
2199 if let Some(project) = project.clone() {
2200 load_uncommitted_diff = Some(
2201 update_uncommitted_diff_for_buffer(
2202 cx.entity(),
2203 &project,
2204 multi_buffer.read(cx).all_buffers(),
2205 multi_buffer.clone(),
2206 cx,
2207 )
2208 .shared(),
2209 );
2210 code_action_providers.push(Rc::new(project) as Rc<_>);
2211 }
2212
2213 let mut editor = Self {
2214 focus_handle,
2215 show_cursor_when_unfocused: false,
2216 last_focused_descendant: None,
2217 buffer: multi_buffer.clone(),
2218 display_map: display_map.clone(),
2219 placeholder_display_map: None,
2220 selections,
2221 scroll_manager: ScrollManager::new(cx),
2222 columnar_selection_state: None,
2223 add_selections_state: None,
2224 select_next_state: None,
2225 select_prev_state: None,
2226 selection_history: SelectionHistory::default(),
2227 defer_selection_effects: false,
2228 deferred_selection_effects_state: None,
2229 autoclose_regions: Vec::new(),
2230 snippet_stack: InvalidationStack::default(),
2231 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2232 ime_transaction: None,
2233 active_diagnostics: ActiveDiagnostic::None,
2234 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2235 inline_diagnostics_update: Task::ready(()),
2236 inline_diagnostics: Vec::new(),
2237 soft_wrap_mode_override,
2238 diagnostics_max_severity,
2239 hard_wrap: None,
2240 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2241 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2242 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2243 project,
2244 blink_manager: blink_manager.clone(),
2245 show_local_selections: true,
2246 show_scrollbars: ScrollbarAxes {
2247 horizontal: full_mode,
2248 vertical: full_mode,
2249 },
2250 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2251 offset_content: !matches!(mode, EditorMode::SingleLine),
2252 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2253 show_gutter: full_mode,
2254 show_line_numbers: (!full_mode).then_some(false),
2255 use_relative_line_numbers: None,
2256 disable_expand_excerpt_buttons: !full_mode,
2257 delegate_expand_excerpts: false,
2258 show_git_diff_gutter: None,
2259 show_code_actions: None,
2260 show_runnables: None,
2261 show_breakpoints: None,
2262 show_diff_review_button: false,
2263 show_wrap_guides: None,
2264 show_indent_guides,
2265 buffers_with_disabled_indent_guides: HashSet::default(),
2266 highlight_order: 0,
2267 highlighted_rows: HashMap::default(),
2268 background_highlights: HashMap::default(),
2269 gutter_highlights: HashMap::default(),
2270 scrollbar_marker_state: ScrollbarMarkerState::default(),
2271 active_indent_guides_state: ActiveIndentGuidesState::default(),
2272 nav_history: None,
2273 context_menu: RefCell::new(None),
2274 context_menu_options: None,
2275 mouse_context_menu: None,
2276 completion_tasks: Vec::new(),
2277 inline_blame_popover: None,
2278 inline_blame_popover_show_task: None,
2279 signature_help_state: SignatureHelpState::default(),
2280 auto_signature_help: None,
2281 find_all_references_task_sources: Vec::new(),
2282 next_completion_id: 0,
2283 next_inlay_id: 0,
2284 code_action_providers,
2285 available_code_actions: None,
2286 code_actions_task: None,
2287 quick_selection_highlight_task: None,
2288 debounced_selection_highlight_task: None,
2289 debounced_selection_highlight_complete: false,
2290 document_highlights_task: None,
2291 linked_editing_range_task: None,
2292 pending_rename: None,
2293 searchable: !is_minimap,
2294 cursor_shape: EditorSettings::get_global(cx)
2295 .cursor_shape
2296 .unwrap_or_default(),
2297 cursor_offset_on_selection: false,
2298 current_line_highlight: None,
2299 autoindent_mode: Some(AutoindentMode::EachLine),
2300 collapse_matches: false,
2301 workspace: None,
2302 input_enabled: !is_minimap,
2303 use_modal_editing: full_mode,
2304 read_only: is_minimap,
2305 use_autoclose: true,
2306 use_auto_surround: true,
2307 auto_replace_emoji_shortcode: false,
2308 jsx_tag_auto_close_enabled_in_any_buffer: false,
2309 leader_id: None,
2310 remote_id: None,
2311 hover_state: HoverState::default(),
2312 pending_mouse_down: None,
2313 prev_pressure_stage: None,
2314 hovered_link_state: None,
2315 edit_prediction_provider: None,
2316 active_edit_prediction: None,
2317 stale_edit_prediction_in_menu: None,
2318 edit_prediction_preview: EditPredictionPreview::Inactive {
2319 released_too_fast: false,
2320 },
2321 inline_diagnostics_enabled: full_mode,
2322 diagnostics_enabled: full_mode,
2323 word_completions_enabled: full_mode,
2324 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2325 gutter_hovered: false,
2326 pixel_position_of_newest_cursor: None,
2327 last_bounds: None,
2328 last_position_map: None,
2329 expect_bounds_change: None,
2330 gutter_dimensions: GutterDimensions::default(),
2331 style: None,
2332 show_cursor_names: false,
2333 hovered_cursors: HashMap::default(),
2334 next_editor_action_id: EditorActionId::default(),
2335 editor_actions: Rc::default(),
2336 edit_predictions_hidden_for_vim_mode: false,
2337 show_edit_predictions_override: None,
2338 show_completions_on_input_override: None,
2339 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2340 edit_prediction_settings: EditPredictionSettings::Disabled,
2341 edit_prediction_indent_conflict: false,
2342 edit_prediction_requires_modifier_in_indent_conflict: true,
2343 custom_context_menu: None,
2344 show_git_blame_gutter: false,
2345 show_git_blame_inline: false,
2346 show_selection_menu: None,
2347 show_git_blame_inline_delay_task: None,
2348 git_blame_inline_enabled: full_mode
2349 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2350 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2351 buffer_serialization: is_minimap.not().then(|| {
2352 BufferSerialization::new(
2353 ProjectSettings::get_global(cx)
2354 .session
2355 .restore_unsaved_buffers,
2356 )
2357 }),
2358 blame: None,
2359 blame_subscription: None,
2360 tasks: BTreeMap::default(),
2361
2362 breakpoint_store,
2363 gutter_breakpoint_indicator: (None, None),
2364 gutter_diff_review_indicator: (None, None),
2365 hovered_diff_hunk_row: None,
2366 _subscriptions: (!is_minimap)
2367 .then(|| {
2368 vec![
2369 cx.observe(&multi_buffer, Self::on_buffer_changed),
2370 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2371 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2372 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2373 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2374 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2375 cx.observe_window_activation(window, |editor, window, cx| {
2376 let active = window.is_window_active();
2377 editor.blink_manager.update(cx, |blink_manager, cx| {
2378 if active {
2379 blink_manager.enable(cx);
2380 } else {
2381 blink_manager.disable(cx);
2382 }
2383 });
2384 if active {
2385 editor.show_mouse_cursor(cx);
2386 }
2387 }),
2388 ]
2389 })
2390 .unwrap_or_default(),
2391 tasks_update_task: None,
2392 pull_diagnostics_task: Task::ready(()),
2393 pull_diagnostics_background_task: Task::ready(()),
2394 colors: None,
2395 refresh_colors_task: Task::ready(()),
2396 inlay_hints: None,
2397 next_color_inlay_id: 0,
2398 post_scroll_update: Task::ready(()),
2399 linked_edit_ranges: Default::default(),
2400 in_project_search: false,
2401 previous_search_ranges: None,
2402 breadcrumb_header: None,
2403 focused_block: None,
2404 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2405 addons: HashMap::default(),
2406 registered_buffers: HashMap::default(),
2407 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2408 selection_mark_mode: false,
2409 toggle_fold_multiple_buffers: Task::ready(()),
2410 serialize_selections: Task::ready(()),
2411 serialize_folds: Task::ready(()),
2412 text_style_refinement: None,
2413 load_diff_task: load_uncommitted_diff,
2414 temporary_diff_override: false,
2415 mouse_cursor_hidden: false,
2416 minimap: None,
2417 hide_mouse_mode: EditorSettings::get_global(cx)
2418 .hide_mouse
2419 .unwrap_or_default(),
2420 change_list: ChangeList::new(),
2421 mode,
2422 selection_drag_state: SelectionDragState::None,
2423 folding_newlines: Task::ready(()),
2424 lookup_key: None,
2425 select_next_is_case_sensitive: None,
2426 applicable_language_settings: HashMap::default(),
2427 accent_data: None,
2428 fetched_tree_sitter_chunks: HashMap::default(),
2429 number_deleted_lines: false,
2430 };
2431
2432 if is_minimap {
2433 return editor;
2434 }
2435
2436 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2437 editor.accent_data = editor.fetch_accent_data(cx);
2438
2439 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2440 editor
2441 ._subscriptions
2442 .push(cx.observe(breakpoints, |_, _, cx| {
2443 cx.notify();
2444 }));
2445 }
2446 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2447 editor._subscriptions.extend(project_subscriptions);
2448
2449 editor._subscriptions.push(cx.subscribe_in(
2450 &cx.entity(),
2451 window,
2452 |editor, _, e: &EditorEvent, window, cx| match e {
2453 EditorEvent::ScrollPositionChanged { local, .. } => {
2454 if *local {
2455 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2456 editor.inline_blame_popover.take();
2457 let new_anchor = editor.scroll_manager.anchor();
2458 let snapshot = editor.snapshot(window, cx);
2459 editor.update_restoration_data(cx, move |data| {
2460 data.scroll_position = (
2461 new_anchor.top_row(snapshot.buffer_snapshot()),
2462 new_anchor.offset,
2463 );
2464 });
2465
2466 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2467 cx.background_executor()
2468 .timer(Duration::from_millis(50))
2469 .await;
2470 editor
2471 .update_in(cx, |editor, window, cx| {
2472 editor.register_visible_buffers(cx);
2473 editor.refresh_colors_for_visible_range(None, window, cx);
2474 editor.refresh_inlay_hints(
2475 InlayHintRefreshReason::NewLinesShown,
2476 cx,
2477 );
2478 editor.colorize_brackets(false, cx);
2479 })
2480 .ok();
2481 });
2482 }
2483 }
2484 EditorEvent::Edited { .. } => {
2485 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2486 .map(|vim_mode| vim_mode.0)
2487 .unwrap_or(false);
2488 if !vim_mode {
2489 let display_map = editor.display_snapshot(cx);
2490 let selections = editor.selections.all_adjusted_display(&display_map);
2491 let pop_state = editor
2492 .change_list
2493 .last()
2494 .map(|previous| {
2495 previous.len() == selections.len()
2496 && previous.iter().enumerate().all(|(ix, p)| {
2497 p.to_display_point(&display_map).row()
2498 == selections[ix].head().row()
2499 })
2500 })
2501 .unwrap_or(false);
2502 let new_positions = selections
2503 .into_iter()
2504 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2505 .collect();
2506 editor
2507 .change_list
2508 .push_to_change_list(pop_state, new_positions);
2509 }
2510 }
2511 _ => (),
2512 },
2513 ));
2514
2515 if let Some(dap_store) = editor
2516 .project
2517 .as_ref()
2518 .map(|project| project.read(cx).dap_store())
2519 {
2520 let weak_editor = cx.weak_entity();
2521
2522 editor
2523 ._subscriptions
2524 .push(
2525 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2526 let session_entity = cx.entity();
2527 weak_editor
2528 .update(cx, |editor, cx| {
2529 editor._subscriptions.push(
2530 cx.subscribe(&session_entity, Self::on_debug_session_event),
2531 );
2532 })
2533 .ok();
2534 }),
2535 );
2536
2537 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2538 editor
2539 ._subscriptions
2540 .push(cx.subscribe(&session, Self::on_debug_session_event));
2541 }
2542 }
2543
2544 // skip adding the initial selection to selection history
2545 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2546 editor.end_selection(window, cx);
2547 editor.selection_history.mode = SelectionHistoryMode::Normal;
2548
2549 editor.scroll_manager.show_scrollbars(window, cx);
2550 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2551
2552 if full_mode {
2553 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2554 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2555
2556 if editor.git_blame_inline_enabled {
2557 editor.start_git_blame_inline(false, window, cx);
2558 }
2559
2560 editor.go_to_active_debug_line(window, cx);
2561
2562 editor.minimap =
2563 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2564 editor.colors = Some(LspColorData::new(cx));
2565 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2566
2567 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2568 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2569 }
2570 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2571 }
2572
2573 editor
2574 }
2575
2576 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2577 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2578 }
2579
2580 pub fn deploy_mouse_context_menu(
2581 &mut self,
2582 position: gpui::Point<Pixels>,
2583 context_menu: Entity<ContextMenu>,
2584 window: &mut Window,
2585 cx: &mut Context<Self>,
2586 ) {
2587 self.mouse_context_menu = Some(MouseContextMenu::new(
2588 self,
2589 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2590 context_menu,
2591 window,
2592 cx,
2593 ));
2594 }
2595
2596 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2597 self.mouse_context_menu
2598 .as_ref()
2599 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2600 }
2601
2602 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2603 if self
2604 .selections
2605 .pending_anchor()
2606 .is_some_and(|pending_selection| {
2607 let snapshot = self.buffer().read(cx).snapshot(cx);
2608 pending_selection.range().includes(range, &snapshot)
2609 })
2610 {
2611 return true;
2612 }
2613
2614 self.selections
2615 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2616 .into_iter()
2617 .any(|selection| {
2618 // This is needed to cover a corner case, if we just check for an existing
2619 // selection in the fold range, having a cursor at the start of the fold
2620 // marks it as selected. Non-empty selections don't cause this.
2621 let length = selection.end - selection.start;
2622 length > 0
2623 })
2624 }
2625
2626 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2627 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2628 }
2629
2630 fn key_context_internal(
2631 &self,
2632 has_active_edit_prediction: bool,
2633 window: &mut Window,
2634 cx: &mut App,
2635 ) -> KeyContext {
2636 let mut key_context = KeyContext::new_with_defaults();
2637 key_context.add("Editor");
2638 let mode = match self.mode {
2639 EditorMode::SingleLine => "single_line",
2640 EditorMode::AutoHeight { .. } => "auto_height",
2641 EditorMode::Minimap { .. } => "minimap",
2642 EditorMode::Full { .. } => "full",
2643 };
2644
2645 if EditorSettings::jupyter_enabled(cx) {
2646 key_context.add("jupyter");
2647 }
2648
2649 key_context.set("mode", mode);
2650 if self.pending_rename.is_some() {
2651 key_context.add("renaming");
2652 }
2653
2654 if let Some(snippet_stack) = self.snippet_stack.last() {
2655 key_context.add("in_snippet");
2656
2657 if snippet_stack.active_index > 0 {
2658 key_context.add("has_previous_tabstop");
2659 }
2660
2661 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2662 key_context.add("has_next_tabstop");
2663 }
2664 }
2665
2666 match self.context_menu.borrow().as_ref() {
2667 Some(CodeContextMenu::Completions(menu)) => {
2668 if menu.visible() {
2669 key_context.add("menu");
2670 key_context.add("showing_completions");
2671 }
2672 }
2673 Some(CodeContextMenu::CodeActions(menu)) => {
2674 if menu.visible() {
2675 key_context.add("menu");
2676 key_context.add("showing_code_actions")
2677 }
2678 }
2679 None => {}
2680 }
2681
2682 if self.signature_help_state.has_multiple_signatures() {
2683 key_context.add("showing_signature_help");
2684 }
2685
2686 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2687 if !self.focus_handle(cx).contains_focused(window, cx)
2688 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2689 {
2690 for addon in self.addons.values() {
2691 addon.extend_key_context(&mut key_context, cx)
2692 }
2693 }
2694
2695 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2696 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2697 Some(
2698 file.full_path(cx)
2699 .extension()?
2700 .to_string_lossy()
2701 .to_lowercase(),
2702 )
2703 }) {
2704 key_context.set("extension", extension);
2705 }
2706 } else {
2707 key_context.add("multibuffer");
2708 }
2709
2710 if has_active_edit_prediction {
2711 if self.edit_prediction_in_conflict() {
2712 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2713 } else {
2714 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2715 key_context.add("copilot_suggestion");
2716 }
2717 }
2718
2719 if self.selection_mark_mode {
2720 key_context.add("selection_mode");
2721 }
2722
2723 let disjoint = self.selections.disjoint_anchors();
2724 let snapshot = self.snapshot(window, cx);
2725 let snapshot = snapshot.buffer_snapshot();
2726 if self.mode == EditorMode::SingleLine
2727 && let [selection] = disjoint
2728 && selection.start == selection.end
2729 && selection.end.to_offset(snapshot) == snapshot.len()
2730 {
2731 key_context.add("end_of_input");
2732 }
2733
2734 if self.has_any_expanded_diff_hunks(cx) {
2735 key_context.add("diffs_expanded");
2736 }
2737
2738 key_context
2739 }
2740
2741 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2742 self.last_bounds.as_ref()
2743 }
2744
2745 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2746 if self.mouse_cursor_hidden {
2747 self.mouse_cursor_hidden = false;
2748 cx.notify();
2749 }
2750 }
2751
2752 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2753 let hide_mouse_cursor = match origin {
2754 HideMouseCursorOrigin::TypingAction => {
2755 matches!(
2756 self.hide_mouse_mode,
2757 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2758 )
2759 }
2760 HideMouseCursorOrigin::MovementAction => {
2761 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2762 }
2763 };
2764 if self.mouse_cursor_hidden != hide_mouse_cursor {
2765 self.mouse_cursor_hidden = hide_mouse_cursor;
2766 cx.notify();
2767 }
2768 }
2769
2770 pub fn edit_prediction_in_conflict(&self) -> bool {
2771 if !self.show_edit_predictions_in_menu() {
2772 return false;
2773 }
2774
2775 let showing_completions = self
2776 .context_menu
2777 .borrow()
2778 .as_ref()
2779 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2780
2781 showing_completions
2782 || self.edit_prediction_requires_modifier()
2783 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2784 // bindings to insert tab characters.
2785 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2786 }
2787
2788 pub fn accept_edit_prediction_keybind(
2789 &self,
2790 granularity: EditPredictionGranularity,
2791 window: &mut Window,
2792 cx: &mut App,
2793 ) -> AcceptEditPredictionBinding {
2794 let key_context = self.key_context_internal(true, window, cx);
2795 let in_conflict = self.edit_prediction_in_conflict();
2796
2797 let bindings =
2798 match granularity {
2799 EditPredictionGranularity::Word => window
2800 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2801 EditPredictionGranularity::Line => window
2802 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2803 EditPredictionGranularity::Full => {
2804 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2805 }
2806 };
2807
2808 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2809 !in_conflict
2810 || binding
2811 .keystrokes()
2812 .first()
2813 .is_some_and(|keystroke| keystroke.modifiers().modified())
2814 }))
2815 }
2816
2817 pub fn new_file(
2818 workspace: &mut Workspace,
2819 _: &workspace::NewFile,
2820 window: &mut Window,
2821 cx: &mut Context<Workspace>,
2822 ) {
2823 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2824 "Failed to create buffer",
2825 window,
2826 cx,
2827 |e, _, _| match e.error_code() {
2828 ErrorCode::RemoteUpgradeRequired => Some(format!(
2829 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2830 e.error_tag("required").unwrap_or("the latest version")
2831 )),
2832 _ => None,
2833 },
2834 );
2835 }
2836
2837 pub fn new_in_workspace(
2838 workspace: &mut Workspace,
2839 window: &mut Window,
2840 cx: &mut Context<Workspace>,
2841 ) -> Task<Result<Entity<Editor>>> {
2842 let project = workspace.project().clone();
2843 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2844
2845 cx.spawn_in(window, async move |workspace, cx| {
2846 let buffer = create.await?;
2847 workspace.update_in(cx, |workspace, window, cx| {
2848 let editor =
2849 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2850 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2851 editor
2852 })
2853 })
2854 }
2855
2856 fn new_file_vertical(
2857 workspace: &mut Workspace,
2858 _: &workspace::NewFileSplitVertical,
2859 window: &mut Window,
2860 cx: &mut Context<Workspace>,
2861 ) {
2862 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2863 }
2864
2865 fn new_file_horizontal(
2866 workspace: &mut Workspace,
2867 _: &workspace::NewFileSplitHorizontal,
2868 window: &mut Window,
2869 cx: &mut Context<Workspace>,
2870 ) {
2871 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2872 }
2873
2874 fn new_file_split(
2875 workspace: &mut Workspace,
2876 action: &workspace::NewFileSplit,
2877 window: &mut Window,
2878 cx: &mut Context<Workspace>,
2879 ) {
2880 Self::new_file_in_direction(workspace, action.0, window, cx)
2881 }
2882
2883 fn new_file_in_direction(
2884 workspace: &mut Workspace,
2885 direction: SplitDirection,
2886 window: &mut Window,
2887 cx: &mut Context<Workspace>,
2888 ) {
2889 let project = workspace.project().clone();
2890 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2891
2892 cx.spawn_in(window, async move |workspace, cx| {
2893 let buffer = create.await?;
2894 workspace.update_in(cx, move |workspace, window, cx| {
2895 workspace.split_item(
2896 direction,
2897 Box::new(
2898 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2899 ),
2900 window,
2901 cx,
2902 )
2903 })?;
2904 anyhow::Ok(())
2905 })
2906 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2907 match e.error_code() {
2908 ErrorCode::RemoteUpgradeRequired => Some(format!(
2909 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2910 e.error_tag("required").unwrap_or("the latest version")
2911 )),
2912 _ => None,
2913 }
2914 });
2915 }
2916
2917 pub fn leader_id(&self) -> Option<CollaboratorId> {
2918 self.leader_id
2919 }
2920
2921 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2922 &self.buffer
2923 }
2924
2925 pub fn project(&self) -> Option<&Entity<Project>> {
2926 self.project.as_ref()
2927 }
2928
2929 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2930 self.workspace.as_ref()?.0.upgrade()
2931 }
2932
2933 /// Returns the workspace serialization ID if this editor should be serialized.
2934 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2935 self.workspace
2936 .as_ref()
2937 .filter(|_| self.should_serialize_buffer())
2938 .and_then(|workspace| workspace.1)
2939 }
2940
2941 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2942 self.buffer().read(cx).title(cx)
2943 }
2944
2945 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2946 let git_blame_gutter_max_author_length = self
2947 .render_git_blame_gutter(cx)
2948 .then(|| {
2949 if let Some(blame) = self.blame.as_ref() {
2950 let max_author_length =
2951 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2952 Some(max_author_length)
2953 } else {
2954 None
2955 }
2956 })
2957 .flatten();
2958
2959 EditorSnapshot {
2960 mode: self.mode.clone(),
2961 show_gutter: self.show_gutter,
2962 offset_content: self.offset_content,
2963 show_line_numbers: self.show_line_numbers,
2964 number_deleted_lines: self.number_deleted_lines,
2965 show_git_diff_gutter: self.show_git_diff_gutter,
2966 show_code_actions: self.show_code_actions,
2967 show_runnables: self.show_runnables,
2968 show_breakpoints: self.show_breakpoints,
2969 git_blame_gutter_max_author_length,
2970 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2971 placeholder_display_snapshot: self
2972 .placeholder_display_map
2973 .as_ref()
2974 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2975 scroll_anchor: self.scroll_manager.anchor(),
2976 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2977 is_focused: self.focus_handle.is_focused(window),
2978 current_line_highlight: self
2979 .current_line_highlight
2980 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2981 gutter_hovered: self.gutter_hovered,
2982 }
2983 }
2984
2985 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2986 self.buffer.read(cx).language_at(point, cx)
2987 }
2988
2989 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2990 self.buffer.read(cx).read(cx).file_at(point).cloned()
2991 }
2992
2993 pub fn active_excerpt(
2994 &self,
2995 cx: &App,
2996 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2997 self.buffer
2998 .read(cx)
2999 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3000 }
3001
3002 pub fn mode(&self) -> &EditorMode {
3003 &self.mode
3004 }
3005
3006 pub fn set_mode(&mut self, mode: EditorMode) {
3007 self.mode = mode;
3008 }
3009
3010 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3011 self.collaboration_hub.as_deref()
3012 }
3013
3014 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3015 self.collaboration_hub = Some(hub);
3016 }
3017
3018 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3019 self.in_project_search = in_project_search;
3020 }
3021
3022 pub fn set_custom_context_menu(
3023 &mut self,
3024 f: impl 'static
3025 + Fn(
3026 &mut Self,
3027 DisplayPoint,
3028 &mut Window,
3029 &mut Context<Self>,
3030 ) -> Option<Entity<ui::ContextMenu>>,
3031 ) {
3032 self.custom_context_menu = Some(Box::new(f))
3033 }
3034
3035 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3036 self.completion_provider = provider;
3037 }
3038
3039 #[cfg(any(test, feature = "test-support"))]
3040 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3041 self.completion_provider.clone()
3042 }
3043
3044 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3045 self.semantics_provider.clone()
3046 }
3047
3048 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3049 self.semantics_provider = provider;
3050 }
3051
3052 pub fn set_edit_prediction_provider<T>(
3053 &mut self,
3054 provider: Option<Entity<T>>,
3055 window: &mut Window,
3056 cx: &mut Context<Self>,
3057 ) where
3058 T: EditPredictionDelegate,
3059 {
3060 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3061 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3062 if this.focus_handle.is_focused(window) {
3063 this.update_visible_edit_prediction(window, cx);
3064 }
3065 }),
3066 provider: Arc::new(provider),
3067 });
3068 self.update_edit_prediction_settings(cx);
3069 self.refresh_edit_prediction(false, false, window, cx);
3070 }
3071
3072 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3073 self.placeholder_display_map
3074 .as_ref()
3075 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3076 }
3077
3078 pub fn set_placeholder_text(
3079 &mut self,
3080 placeholder_text: &str,
3081 window: &mut Window,
3082 cx: &mut Context<Self>,
3083 ) {
3084 let multibuffer = cx
3085 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3086
3087 let style = window.text_style();
3088
3089 self.placeholder_display_map = Some(cx.new(|cx| {
3090 DisplayMap::new(
3091 multibuffer,
3092 style.font(),
3093 style.font_size.to_pixels(window.rem_size()),
3094 None,
3095 FILE_HEADER_HEIGHT,
3096 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3097 Default::default(),
3098 DiagnosticSeverity::Off,
3099 cx,
3100 )
3101 }));
3102 cx.notify();
3103 }
3104
3105 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3106 self.cursor_shape = cursor_shape;
3107
3108 // Disrupt blink for immediate user feedback that the cursor shape has changed
3109 self.blink_manager.update(cx, BlinkManager::show_cursor);
3110
3111 cx.notify();
3112 }
3113
3114 pub fn cursor_shape(&self) -> CursorShape {
3115 self.cursor_shape
3116 }
3117
3118 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3119 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3120 }
3121
3122 pub fn set_current_line_highlight(
3123 &mut self,
3124 current_line_highlight: Option<CurrentLineHighlight>,
3125 ) {
3126 self.current_line_highlight = current_line_highlight;
3127 }
3128
3129 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3130 self.collapse_matches = collapse_matches;
3131 }
3132
3133 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3134 if self.collapse_matches {
3135 return range.start..range.start;
3136 }
3137 range.clone()
3138 }
3139
3140 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3141 self.display_map.read(cx).clip_at_line_ends
3142 }
3143
3144 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3145 if self.display_map.read(cx).clip_at_line_ends != clip {
3146 self.display_map
3147 .update(cx, |map, _| map.clip_at_line_ends = clip);
3148 }
3149 }
3150
3151 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3152 self.input_enabled = input_enabled;
3153 }
3154
3155 pub fn set_edit_predictions_hidden_for_vim_mode(
3156 &mut self,
3157 hidden: bool,
3158 window: &mut Window,
3159 cx: &mut Context<Self>,
3160 ) {
3161 if hidden != self.edit_predictions_hidden_for_vim_mode {
3162 self.edit_predictions_hidden_for_vim_mode = hidden;
3163 if hidden {
3164 self.update_visible_edit_prediction(window, cx);
3165 } else {
3166 self.refresh_edit_prediction(true, false, window, cx);
3167 }
3168 }
3169 }
3170
3171 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3172 self.menu_edit_predictions_policy = value;
3173 }
3174
3175 pub fn set_autoindent(&mut self, autoindent: bool) {
3176 if autoindent {
3177 self.autoindent_mode = Some(AutoindentMode::EachLine);
3178 } else {
3179 self.autoindent_mode = None;
3180 }
3181 }
3182
3183 pub fn capability(&self, cx: &App) -> Capability {
3184 if self.read_only {
3185 Capability::ReadOnly
3186 } else {
3187 self.buffer.read(cx).capability()
3188 }
3189 }
3190
3191 pub fn read_only(&self, cx: &App) -> bool {
3192 self.read_only || self.buffer.read(cx).read_only()
3193 }
3194
3195 pub fn set_read_only(&mut self, read_only: bool) {
3196 self.read_only = read_only;
3197 }
3198
3199 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3200 self.use_autoclose = autoclose;
3201 }
3202
3203 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3204 self.use_auto_surround = auto_surround;
3205 }
3206
3207 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3208 self.auto_replace_emoji_shortcode = auto_replace;
3209 }
3210
3211 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3212 self.buffer_serialization = should_serialize.then(|| {
3213 BufferSerialization::new(
3214 ProjectSettings::get_global(cx)
3215 .session
3216 .restore_unsaved_buffers,
3217 )
3218 })
3219 }
3220
3221 fn should_serialize_buffer(&self) -> bool {
3222 self.buffer_serialization.is_some()
3223 }
3224
3225 pub fn toggle_edit_predictions(
3226 &mut self,
3227 _: &ToggleEditPrediction,
3228 window: &mut Window,
3229 cx: &mut Context<Self>,
3230 ) {
3231 if self.show_edit_predictions_override.is_some() {
3232 self.set_show_edit_predictions(None, window, cx);
3233 } else {
3234 let show_edit_predictions = !self.edit_predictions_enabled();
3235 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3236 }
3237 }
3238
3239 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3240 self.show_completions_on_input_override = show_completions_on_input;
3241 }
3242
3243 pub fn set_show_edit_predictions(
3244 &mut self,
3245 show_edit_predictions: Option<bool>,
3246 window: &mut Window,
3247 cx: &mut Context<Self>,
3248 ) {
3249 self.show_edit_predictions_override = show_edit_predictions;
3250 self.update_edit_prediction_settings(cx);
3251
3252 if let Some(false) = show_edit_predictions {
3253 self.discard_edit_prediction(false, cx);
3254 } else {
3255 self.refresh_edit_prediction(false, true, window, cx);
3256 }
3257 }
3258
3259 fn edit_predictions_disabled_in_scope(
3260 &self,
3261 buffer: &Entity<Buffer>,
3262 buffer_position: language::Anchor,
3263 cx: &App,
3264 ) -> bool {
3265 let snapshot = buffer.read(cx).snapshot();
3266 let settings = snapshot.settings_at(buffer_position, cx);
3267
3268 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3269 return false;
3270 };
3271
3272 scope.override_name().is_some_and(|scope_name| {
3273 settings
3274 .edit_predictions_disabled_in
3275 .iter()
3276 .any(|s| s == scope_name)
3277 })
3278 }
3279
3280 pub fn set_use_modal_editing(&mut self, to: bool) {
3281 self.use_modal_editing = to;
3282 }
3283
3284 pub fn use_modal_editing(&self) -> bool {
3285 self.use_modal_editing
3286 }
3287
3288 fn selections_did_change(
3289 &mut self,
3290 local: bool,
3291 old_cursor_position: &Anchor,
3292 effects: SelectionEffects,
3293 window: &mut Window,
3294 cx: &mut Context<Self>,
3295 ) {
3296 window.invalidate_character_coordinates();
3297
3298 // Copy selections to primary selection buffer
3299 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3300 if local {
3301 let selections = self
3302 .selections
3303 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3304 let buffer_handle = self.buffer.read(cx).read(cx);
3305
3306 let mut text = String::new();
3307 for (index, selection) in selections.iter().enumerate() {
3308 let text_for_selection = buffer_handle
3309 .text_for_range(selection.start..selection.end)
3310 .collect::<String>();
3311
3312 text.push_str(&text_for_selection);
3313 if index != selections.len() - 1 {
3314 text.push('\n');
3315 }
3316 }
3317
3318 if !text.is_empty() {
3319 cx.write_to_primary(ClipboardItem::new_string(text));
3320 }
3321 }
3322
3323 let selection_anchors = self.selections.disjoint_anchors_arc();
3324
3325 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3326 self.buffer.update(cx, |buffer, cx| {
3327 buffer.set_active_selections(
3328 &selection_anchors,
3329 self.selections.line_mode(),
3330 self.cursor_shape,
3331 cx,
3332 )
3333 });
3334 }
3335 let display_map = self
3336 .display_map
3337 .update(cx, |display_map, cx| display_map.snapshot(cx));
3338 let buffer = display_map.buffer_snapshot();
3339 if self.selections.count() == 1 {
3340 self.add_selections_state = None;
3341 }
3342 self.select_next_state = None;
3343 self.select_prev_state = None;
3344 self.select_syntax_node_history.try_clear();
3345 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3346 self.snippet_stack.invalidate(&selection_anchors, buffer);
3347 self.take_rename(false, window, cx);
3348
3349 let newest_selection = self.selections.newest_anchor();
3350 let new_cursor_position = newest_selection.head();
3351 let selection_start = newest_selection.start;
3352
3353 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3354 self.push_to_nav_history(
3355 *old_cursor_position,
3356 Some(new_cursor_position.to_point(buffer)),
3357 false,
3358 effects.nav_history == Some(true),
3359 cx,
3360 );
3361 }
3362
3363 if local {
3364 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3365 self.register_buffer(buffer_id, cx);
3366 }
3367
3368 let mut context_menu = self.context_menu.borrow_mut();
3369 let completion_menu = match context_menu.as_ref() {
3370 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3371 Some(CodeContextMenu::CodeActions(_)) => {
3372 *context_menu = None;
3373 None
3374 }
3375 None => None,
3376 };
3377 let completion_position = completion_menu.map(|menu| menu.initial_position);
3378 drop(context_menu);
3379
3380 if effects.completions
3381 && let Some(completion_position) = completion_position
3382 {
3383 let start_offset = selection_start.to_offset(buffer);
3384 let position_matches = start_offset == completion_position.to_offset(buffer);
3385 let continue_showing = if let Some((snap, ..)) =
3386 buffer.point_to_buffer_offset(completion_position)
3387 && !snap.capability.editable()
3388 {
3389 false
3390 } else if position_matches {
3391 if self.snippet_stack.is_empty() {
3392 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3393 == Some(CharKind::Word)
3394 } else {
3395 // Snippet choices can be shown even when the cursor is in whitespace.
3396 // Dismissing the menu with actions like backspace is handled by
3397 // invalidation regions.
3398 true
3399 }
3400 } else {
3401 false
3402 };
3403
3404 if continue_showing {
3405 self.open_or_update_completions_menu(None, None, false, window, cx);
3406 } else {
3407 self.hide_context_menu(window, cx);
3408 }
3409 }
3410
3411 hide_hover(self, cx);
3412
3413 if old_cursor_position.to_display_point(&display_map).row()
3414 != new_cursor_position.to_display_point(&display_map).row()
3415 {
3416 self.available_code_actions.take();
3417 }
3418 self.refresh_code_actions(window, cx);
3419 self.refresh_document_highlights(cx);
3420 refresh_linked_ranges(self, window, cx);
3421
3422 self.refresh_selected_text_highlights(false, window, cx);
3423 self.refresh_matching_bracket_highlights(window, cx);
3424 self.update_visible_edit_prediction(window, cx);
3425 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3426 self.inline_blame_popover.take();
3427 if self.git_blame_inline_enabled {
3428 self.start_inline_blame_timer(window, cx);
3429 }
3430 }
3431
3432 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3433 cx.emit(EditorEvent::SelectionsChanged { local });
3434
3435 let selections = &self.selections.disjoint_anchors_arc();
3436 if selections.len() == 1 {
3437 cx.emit(SearchEvent::ActiveMatchChanged)
3438 }
3439 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3440 let inmemory_selections = selections
3441 .iter()
3442 .map(|s| {
3443 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3444 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3445 })
3446 .collect();
3447 self.update_restoration_data(cx, |data| {
3448 data.selections = inmemory_selections;
3449 });
3450
3451 if WorkspaceSettings::get(None, cx).restore_on_startup
3452 != RestoreOnStartupBehavior::EmptyTab
3453 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3454 {
3455 let snapshot = self.buffer().read(cx).snapshot(cx);
3456 let selections = selections.clone();
3457 let background_executor = cx.background_executor().clone();
3458 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3459 self.serialize_selections = cx.background_spawn(async move {
3460 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3461 let db_selections = selections
3462 .iter()
3463 .map(|selection| {
3464 (
3465 selection.start.to_offset(&snapshot).0,
3466 selection.end.to_offset(&snapshot).0,
3467 )
3468 })
3469 .collect();
3470
3471 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3472 .await
3473 .with_context(|| {
3474 format!(
3475 "persisting editor selections for editor {editor_id}, \
3476 workspace {workspace_id:?}"
3477 )
3478 })
3479 .log_err();
3480 });
3481 }
3482 }
3483
3484 cx.notify();
3485 }
3486
3487 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3488 use text::ToOffset as _;
3489 use text::ToPoint as _;
3490
3491 if self.mode.is_minimap()
3492 || WorkspaceSettings::get(None, cx).restore_on_startup
3493 == RestoreOnStartupBehavior::EmptyTab
3494 {
3495 return;
3496 }
3497
3498 if !self.buffer().read(cx).is_singleton() {
3499 return;
3500 }
3501
3502 let display_snapshot = self
3503 .display_map
3504 .update(cx, |display_map, cx| display_map.snapshot(cx));
3505 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3506 return;
3507 };
3508 let inmemory_folds = display_snapshot
3509 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3510 .map(|fold| {
3511 fold.range.start.text_anchor.to_point(&snapshot)
3512 ..fold.range.end.text_anchor.to_point(&snapshot)
3513 })
3514 .collect();
3515 self.update_restoration_data(cx, |data| {
3516 data.folds = inmemory_folds;
3517 });
3518
3519 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3520 return;
3521 };
3522 let background_executor = cx.background_executor().clone();
3523 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3524 const FINGERPRINT_LEN: usize = 32;
3525 let db_folds = display_snapshot
3526 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3527 .map(|fold| {
3528 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3529 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3530
3531 // Extract fingerprints - content at fold boundaries for validation on restore
3532 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3533 // content that might change independently.
3534 // start_fp: first min(32, fold_len) bytes of fold content
3535 // end_fp: last min(32, fold_len) bytes of fold content
3536 // Clip to character boundaries to handle multibyte UTF-8 characters.
3537 let fold_len = end - start;
3538 let start_fp_end = snapshot
3539 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3540 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3541 let end_fp_start = snapshot
3542 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3543 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3544
3545 (start, end, start_fp, end_fp)
3546 })
3547 .collect::<Vec<_>>();
3548 self.serialize_folds = cx.background_spawn(async move {
3549 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3550 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3551 .await
3552 .with_context(|| {
3553 format!(
3554 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3555 )
3556 })
3557 .log_err();
3558 });
3559 }
3560
3561 pub fn sync_selections(
3562 &mut self,
3563 other: Entity<Editor>,
3564 cx: &mut Context<Self>,
3565 ) -> gpui::Subscription {
3566 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3567 if !other_selections.is_empty() {
3568 self.selections
3569 .change_with(&self.display_snapshot(cx), |selections| {
3570 selections.select_anchors(other_selections);
3571 });
3572 }
3573
3574 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3575 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3576 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3577 if other_selections.is_empty() {
3578 return;
3579 }
3580 let snapshot = this.display_snapshot(cx);
3581 this.selections.change_with(&snapshot, |selections| {
3582 selections.select_anchors(other_selections);
3583 });
3584 }
3585 });
3586
3587 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3588 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3589 let these_selections = this.selections.disjoint_anchors().to_vec();
3590 if these_selections.is_empty() {
3591 return;
3592 }
3593 other.update(cx, |other_editor, cx| {
3594 let snapshot = other_editor.display_snapshot(cx);
3595 other_editor
3596 .selections
3597 .change_with(&snapshot, |selections| {
3598 selections.select_anchors(these_selections);
3599 })
3600 });
3601 }
3602 });
3603
3604 Subscription::join(other_subscription, this_subscription)
3605 }
3606
3607 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3608 if self.buffer().read(cx).is_singleton() {
3609 return;
3610 }
3611 let snapshot = self.buffer.read(cx).snapshot(cx);
3612 let buffer_ids: HashSet<BufferId> = self
3613 .selections
3614 .disjoint_anchor_ranges()
3615 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3616 .collect();
3617 for buffer_id in buffer_ids {
3618 self.unfold_buffer(buffer_id, cx);
3619 }
3620 }
3621
3622 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3623 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3624 /// effects of selection change occur at the end of the transaction.
3625 pub fn change_selections<R>(
3626 &mut self,
3627 effects: SelectionEffects,
3628 window: &mut Window,
3629 cx: &mut Context<Self>,
3630 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3631 ) -> R {
3632 let snapshot = self.display_snapshot(cx);
3633 if let Some(state) = &mut self.deferred_selection_effects_state {
3634 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3635 state.effects.completions = effects.completions;
3636 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3637 let (changed, result) = self.selections.change_with(&snapshot, change);
3638 state.changed |= changed;
3639 return result;
3640 }
3641 let mut state = DeferredSelectionEffectsState {
3642 changed: false,
3643 effects,
3644 old_cursor_position: self.selections.newest_anchor().head(),
3645 history_entry: SelectionHistoryEntry {
3646 selections: self.selections.disjoint_anchors_arc(),
3647 select_next_state: self.select_next_state.clone(),
3648 select_prev_state: self.select_prev_state.clone(),
3649 add_selections_state: self.add_selections_state.clone(),
3650 },
3651 };
3652 let (changed, result) = self.selections.change_with(&snapshot, change);
3653 state.changed = state.changed || changed;
3654 if self.defer_selection_effects {
3655 self.deferred_selection_effects_state = Some(state);
3656 } else {
3657 self.apply_selection_effects(state, window, cx);
3658 }
3659 result
3660 }
3661
3662 /// Defers the effects of selection change, so that the effects of multiple calls to
3663 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3664 /// to selection history and the state of popovers based on selection position aren't
3665 /// erroneously updated.
3666 pub fn with_selection_effects_deferred<R>(
3667 &mut self,
3668 window: &mut Window,
3669 cx: &mut Context<Self>,
3670 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3671 ) -> R {
3672 let already_deferred = self.defer_selection_effects;
3673 self.defer_selection_effects = true;
3674 let result = update(self, window, cx);
3675 if !already_deferred {
3676 self.defer_selection_effects = false;
3677 if let Some(state) = self.deferred_selection_effects_state.take() {
3678 self.apply_selection_effects(state, window, cx);
3679 }
3680 }
3681 result
3682 }
3683
3684 fn apply_selection_effects(
3685 &mut self,
3686 state: DeferredSelectionEffectsState,
3687 window: &mut Window,
3688 cx: &mut Context<Self>,
3689 ) {
3690 if state.changed {
3691 self.selection_history.push(state.history_entry);
3692
3693 if let Some(autoscroll) = state.effects.scroll {
3694 self.request_autoscroll(autoscroll, cx);
3695 }
3696
3697 let old_cursor_position = &state.old_cursor_position;
3698
3699 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3700
3701 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3702 self.show_signature_help(&ShowSignatureHelp, window, cx);
3703 }
3704 }
3705 }
3706
3707 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3708 where
3709 I: IntoIterator<Item = (Range<S>, T)>,
3710 S: ToOffset,
3711 T: Into<Arc<str>>,
3712 {
3713 if self.read_only(cx) {
3714 return;
3715 }
3716
3717 self.buffer
3718 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3719 }
3720
3721 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3722 where
3723 I: IntoIterator<Item = (Range<S>, T)>,
3724 S: ToOffset,
3725 T: Into<Arc<str>>,
3726 {
3727 if self.read_only(cx) {
3728 return;
3729 }
3730
3731 self.buffer.update(cx, |buffer, cx| {
3732 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3733 });
3734 }
3735
3736 pub fn edit_with_block_indent<I, S, T>(
3737 &mut self,
3738 edits: I,
3739 original_indent_columns: Vec<Option<u32>>,
3740 cx: &mut Context<Self>,
3741 ) where
3742 I: IntoIterator<Item = (Range<S>, T)>,
3743 S: ToOffset,
3744 T: Into<Arc<str>>,
3745 {
3746 if self.read_only(cx) {
3747 return;
3748 }
3749
3750 self.buffer.update(cx, |buffer, cx| {
3751 buffer.edit(
3752 edits,
3753 Some(AutoindentMode::Block {
3754 original_indent_columns,
3755 }),
3756 cx,
3757 )
3758 });
3759 }
3760
3761 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3762 self.hide_context_menu(window, cx);
3763
3764 match phase {
3765 SelectPhase::Begin {
3766 position,
3767 add,
3768 click_count,
3769 } => self.begin_selection(position, add, click_count, window, cx),
3770 SelectPhase::BeginColumnar {
3771 position,
3772 goal_column,
3773 reset,
3774 mode,
3775 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3776 SelectPhase::Extend {
3777 position,
3778 click_count,
3779 } => self.extend_selection(position, click_count, window, cx),
3780 SelectPhase::Update {
3781 position,
3782 goal_column,
3783 scroll_delta,
3784 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3785 SelectPhase::End => self.end_selection(window, cx),
3786 }
3787 }
3788
3789 fn extend_selection(
3790 &mut self,
3791 position: DisplayPoint,
3792 click_count: usize,
3793 window: &mut Window,
3794 cx: &mut Context<Self>,
3795 ) {
3796 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3797 let tail = self
3798 .selections
3799 .newest::<MultiBufferOffset>(&display_map)
3800 .tail();
3801 let click_count = click_count.max(match self.selections.select_mode() {
3802 SelectMode::Character => 1,
3803 SelectMode::Word(_) => 2,
3804 SelectMode::Line(_) => 3,
3805 SelectMode::All => 4,
3806 });
3807 self.begin_selection(position, false, click_count, window, cx);
3808
3809 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3810
3811 let current_selection = match self.selections.select_mode() {
3812 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3813 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3814 };
3815
3816 let mut pending_selection = self
3817 .selections
3818 .pending_anchor()
3819 .cloned()
3820 .expect("extend_selection not called with pending selection");
3821
3822 if pending_selection
3823 .start
3824 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3825 == Ordering::Greater
3826 {
3827 pending_selection.start = current_selection.start;
3828 }
3829 if pending_selection
3830 .end
3831 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3832 == Ordering::Less
3833 {
3834 pending_selection.end = current_selection.end;
3835 pending_selection.reversed = true;
3836 }
3837
3838 let mut pending_mode = self.selections.pending_mode().unwrap();
3839 match &mut pending_mode {
3840 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3841 _ => {}
3842 }
3843
3844 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3845 SelectionEffects::scroll(Autoscroll::fit())
3846 } else {
3847 SelectionEffects::no_scroll()
3848 };
3849
3850 self.change_selections(effects, window, cx, |s| {
3851 s.set_pending(pending_selection.clone(), pending_mode);
3852 s.set_is_extending(true);
3853 });
3854 }
3855
3856 fn begin_selection(
3857 &mut self,
3858 position: DisplayPoint,
3859 add: bool,
3860 click_count: usize,
3861 window: &mut Window,
3862 cx: &mut Context<Self>,
3863 ) {
3864 if !self.focus_handle.is_focused(window) {
3865 self.last_focused_descendant = None;
3866 window.focus(&self.focus_handle, cx);
3867 }
3868
3869 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3870 let buffer = display_map.buffer_snapshot();
3871 let position = display_map.clip_point(position, Bias::Left);
3872
3873 let start;
3874 let end;
3875 let mode;
3876 let mut auto_scroll;
3877 match click_count {
3878 1 => {
3879 start = buffer.anchor_before(position.to_point(&display_map));
3880 end = start;
3881 mode = SelectMode::Character;
3882 auto_scroll = true;
3883 }
3884 2 => {
3885 let position = display_map
3886 .clip_point(position, Bias::Left)
3887 .to_offset(&display_map, Bias::Left);
3888 let (range, _) = buffer.surrounding_word(position, None);
3889 start = buffer.anchor_before(range.start);
3890 end = buffer.anchor_before(range.end);
3891 mode = SelectMode::Word(start..end);
3892 auto_scroll = true;
3893 }
3894 3 => {
3895 let position = display_map
3896 .clip_point(position, Bias::Left)
3897 .to_point(&display_map);
3898 let line_start = display_map.prev_line_boundary(position).0;
3899 let next_line_start = buffer.clip_point(
3900 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3901 Bias::Left,
3902 );
3903 start = buffer.anchor_before(line_start);
3904 end = buffer.anchor_before(next_line_start);
3905 mode = SelectMode::Line(start..end);
3906 auto_scroll = true;
3907 }
3908 _ => {
3909 start = buffer.anchor_before(MultiBufferOffset(0));
3910 end = buffer.anchor_before(buffer.len());
3911 mode = SelectMode::All;
3912 auto_scroll = false;
3913 }
3914 }
3915 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3916
3917 let point_to_delete: Option<usize> = {
3918 let selected_points: Vec<Selection<Point>> =
3919 self.selections.disjoint_in_range(start..end, &display_map);
3920
3921 if !add || click_count > 1 {
3922 None
3923 } else if !selected_points.is_empty() {
3924 Some(selected_points[0].id)
3925 } else {
3926 let clicked_point_already_selected =
3927 self.selections.disjoint_anchors().iter().find(|selection| {
3928 selection.start.to_point(buffer) == start.to_point(buffer)
3929 || selection.end.to_point(buffer) == end.to_point(buffer)
3930 });
3931
3932 clicked_point_already_selected.map(|selection| selection.id)
3933 }
3934 };
3935
3936 let selections_count = self.selections.count();
3937 let effects = if auto_scroll {
3938 SelectionEffects::default()
3939 } else {
3940 SelectionEffects::no_scroll()
3941 };
3942
3943 self.change_selections(effects, window, cx, |s| {
3944 if let Some(point_to_delete) = point_to_delete {
3945 s.delete(point_to_delete);
3946
3947 if selections_count == 1 {
3948 s.set_pending_anchor_range(start..end, mode);
3949 }
3950 } else {
3951 if !add {
3952 s.clear_disjoint();
3953 }
3954
3955 s.set_pending_anchor_range(start..end, mode);
3956 }
3957 });
3958 }
3959
3960 fn begin_columnar_selection(
3961 &mut self,
3962 position: DisplayPoint,
3963 goal_column: u32,
3964 reset: bool,
3965 mode: ColumnarMode,
3966 window: &mut Window,
3967 cx: &mut Context<Self>,
3968 ) {
3969 if !self.focus_handle.is_focused(window) {
3970 self.last_focused_descendant = None;
3971 window.focus(&self.focus_handle, cx);
3972 }
3973
3974 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3975
3976 if reset {
3977 let pointer_position = display_map
3978 .buffer_snapshot()
3979 .anchor_before(position.to_point(&display_map));
3980
3981 self.change_selections(
3982 SelectionEffects::scroll(Autoscroll::newest()),
3983 window,
3984 cx,
3985 |s| {
3986 s.clear_disjoint();
3987 s.set_pending_anchor_range(
3988 pointer_position..pointer_position,
3989 SelectMode::Character,
3990 );
3991 },
3992 );
3993 };
3994
3995 let tail = self.selections.newest::<Point>(&display_map).tail();
3996 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3997 self.columnar_selection_state = match mode {
3998 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3999 selection_tail: selection_anchor,
4000 display_point: if reset {
4001 if position.column() != goal_column {
4002 Some(DisplayPoint::new(position.row(), goal_column))
4003 } else {
4004 None
4005 }
4006 } else {
4007 None
4008 },
4009 }),
4010 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4011 selection_tail: selection_anchor,
4012 }),
4013 };
4014
4015 if !reset {
4016 self.select_columns(position, goal_column, &display_map, window, cx);
4017 }
4018 }
4019
4020 fn update_selection(
4021 &mut self,
4022 position: DisplayPoint,
4023 goal_column: u32,
4024 scroll_delta: gpui::Point<f32>,
4025 window: &mut Window,
4026 cx: &mut Context<Self>,
4027 ) {
4028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4029
4030 if self.columnar_selection_state.is_some() {
4031 self.select_columns(position, goal_column, &display_map, window, cx);
4032 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4033 let buffer = display_map.buffer_snapshot();
4034 let head;
4035 let tail;
4036 let mode = self.selections.pending_mode().unwrap();
4037 match &mode {
4038 SelectMode::Character => {
4039 head = position.to_point(&display_map);
4040 tail = pending.tail().to_point(buffer);
4041 }
4042 SelectMode::Word(original_range) => {
4043 let offset = display_map
4044 .clip_point(position, Bias::Left)
4045 .to_offset(&display_map, Bias::Left);
4046 let original_range = original_range.to_offset(buffer);
4047
4048 let head_offset = if buffer.is_inside_word(offset, None)
4049 || original_range.contains(&offset)
4050 {
4051 let (word_range, _) = buffer.surrounding_word(offset, None);
4052 if word_range.start < original_range.start {
4053 word_range.start
4054 } else {
4055 word_range.end
4056 }
4057 } else {
4058 offset
4059 };
4060
4061 head = head_offset.to_point(buffer);
4062 if head_offset <= original_range.start {
4063 tail = original_range.end.to_point(buffer);
4064 } else {
4065 tail = original_range.start.to_point(buffer);
4066 }
4067 }
4068 SelectMode::Line(original_range) => {
4069 let original_range = original_range.to_point(display_map.buffer_snapshot());
4070
4071 let position = display_map
4072 .clip_point(position, Bias::Left)
4073 .to_point(&display_map);
4074 let line_start = display_map.prev_line_boundary(position).0;
4075 let next_line_start = buffer.clip_point(
4076 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4077 Bias::Left,
4078 );
4079
4080 if line_start < original_range.start {
4081 head = line_start
4082 } else {
4083 head = next_line_start
4084 }
4085
4086 if head <= original_range.start {
4087 tail = original_range.end;
4088 } else {
4089 tail = original_range.start;
4090 }
4091 }
4092 SelectMode::All => {
4093 return;
4094 }
4095 };
4096
4097 if head < tail {
4098 pending.start = buffer.anchor_before(head);
4099 pending.end = buffer.anchor_before(tail);
4100 pending.reversed = true;
4101 } else {
4102 pending.start = buffer.anchor_before(tail);
4103 pending.end = buffer.anchor_before(head);
4104 pending.reversed = false;
4105 }
4106
4107 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4108 s.set_pending(pending.clone(), mode);
4109 });
4110 } else {
4111 log::error!("update_selection dispatched with no pending selection");
4112 return;
4113 }
4114
4115 self.apply_scroll_delta(scroll_delta, window, cx);
4116 cx.notify();
4117 }
4118
4119 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4120 self.columnar_selection_state.take();
4121 if let Some(pending_mode) = self.selections.pending_mode() {
4122 let selections = self
4123 .selections
4124 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4125 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4126 s.select(selections);
4127 s.clear_pending();
4128 if s.is_extending() {
4129 s.set_is_extending(false);
4130 } else {
4131 s.set_select_mode(pending_mode);
4132 }
4133 });
4134 }
4135 }
4136
4137 fn select_columns(
4138 &mut self,
4139 head: DisplayPoint,
4140 goal_column: u32,
4141 display_map: &DisplaySnapshot,
4142 window: &mut Window,
4143 cx: &mut Context<Self>,
4144 ) {
4145 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4146 return;
4147 };
4148
4149 let tail = match columnar_state {
4150 ColumnarSelectionState::FromMouse {
4151 selection_tail,
4152 display_point,
4153 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4154 ColumnarSelectionState::FromSelection { selection_tail } => {
4155 selection_tail.to_display_point(display_map)
4156 }
4157 };
4158
4159 let start_row = cmp::min(tail.row(), head.row());
4160 let end_row = cmp::max(tail.row(), head.row());
4161 let start_column = cmp::min(tail.column(), goal_column);
4162 let end_column = cmp::max(tail.column(), goal_column);
4163 let reversed = start_column < tail.column();
4164
4165 let selection_ranges = (start_row.0..=end_row.0)
4166 .map(DisplayRow)
4167 .filter_map(|row| {
4168 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4169 || start_column <= display_map.line_len(row))
4170 && !display_map.is_block_line(row)
4171 {
4172 let start = display_map
4173 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4174 .to_point(display_map);
4175 let end = display_map
4176 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4177 .to_point(display_map);
4178 if reversed {
4179 Some(end..start)
4180 } else {
4181 Some(start..end)
4182 }
4183 } else {
4184 None
4185 }
4186 })
4187 .collect::<Vec<_>>();
4188 if selection_ranges.is_empty() {
4189 return;
4190 }
4191
4192 let ranges = match columnar_state {
4193 ColumnarSelectionState::FromMouse { .. } => {
4194 let mut non_empty_ranges = selection_ranges
4195 .iter()
4196 .filter(|selection_range| selection_range.start != selection_range.end)
4197 .peekable();
4198 if non_empty_ranges.peek().is_some() {
4199 non_empty_ranges.cloned().collect()
4200 } else {
4201 selection_ranges
4202 }
4203 }
4204 _ => selection_ranges,
4205 };
4206
4207 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4208 s.select_ranges(ranges);
4209 });
4210 cx.notify();
4211 }
4212
4213 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4214 self.selections
4215 .all_adjusted(snapshot)
4216 .iter()
4217 .any(|selection| !selection.is_empty())
4218 }
4219
4220 pub fn has_pending_nonempty_selection(&self) -> bool {
4221 let pending_nonempty_selection = match self.selections.pending_anchor() {
4222 Some(Selection { start, end, .. }) => start != end,
4223 None => false,
4224 };
4225
4226 pending_nonempty_selection
4227 || (self.columnar_selection_state.is_some()
4228 && self.selections.disjoint_anchors().len() > 1)
4229 }
4230
4231 pub fn has_pending_selection(&self) -> bool {
4232 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4233 }
4234
4235 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4236 self.selection_mark_mode = false;
4237 self.selection_drag_state = SelectionDragState::None;
4238
4239 if self.dismiss_menus_and_popups(true, window, cx) {
4240 cx.notify();
4241 return;
4242 }
4243 if self.clear_expanded_diff_hunks(cx) {
4244 cx.notify();
4245 return;
4246 }
4247 if self.show_git_blame_gutter {
4248 self.show_git_blame_gutter = false;
4249 cx.notify();
4250 return;
4251 }
4252
4253 if self.mode.is_full()
4254 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4255 {
4256 cx.notify();
4257 return;
4258 }
4259
4260 cx.propagate();
4261 }
4262
4263 pub fn dismiss_menus_and_popups(
4264 &mut self,
4265 is_user_requested: bool,
4266 window: &mut Window,
4267 cx: &mut Context<Self>,
4268 ) -> bool {
4269 let mut dismissed = false;
4270
4271 dismissed |= self.take_rename(false, window, cx).is_some();
4272 dismissed |= self.hide_blame_popover(true, cx);
4273 dismissed |= hide_hover(self, cx);
4274 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4275 dismissed |= self.hide_context_menu(window, cx).is_some();
4276 dismissed |= self.mouse_context_menu.take().is_some();
4277 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4278 dismissed |= self.snippet_stack.pop().is_some();
4279
4280 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4281 self.dismiss_diagnostics(cx);
4282 dismissed = true;
4283 }
4284
4285 dismissed
4286 }
4287
4288 fn linked_editing_ranges_for(
4289 &self,
4290 selection: Range<text::Anchor>,
4291 cx: &App,
4292 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4293 if self.linked_edit_ranges.is_empty() {
4294 return None;
4295 }
4296 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4297 selection.end.buffer_id.and_then(|end_buffer_id| {
4298 if selection.start.buffer_id != Some(end_buffer_id) {
4299 return None;
4300 }
4301 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4302 let snapshot = buffer.read(cx).snapshot();
4303 self.linked_edit_ranges
4304 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4305 .map(|ranges| (ranges, snapshot, buffer))
4306 })?;
4307 use text::ToOffset as TO;
4308 // find offset from the start of current range to current cursor position
4309 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4310
4311 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4312 let start_difference = start_offset - start_byte_offset;
4313 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4314 let end_difference = end_offset - start_byte_offset;
4315 // Current range has associated linked ranges.
4316 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4317 for range in linked_ranges.iter() {
4318 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4319 let end_offset = start_offset + end_difference;
4320 let start_offset = start_offset + start_difference;
4321 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4322 continue;
4323 }
4324 if self.selections.disjoint_anchor_ranges().any(|s| {
4325 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4326 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4327 {
4328 return false;
4329 }
4330 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4331 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4332 }) {
4333 continue;
4334 }
4335 let start = buffer_snapshot.anchor_after(start_offset);
4336 let end = buffer_snapshot.anchor_after(end_offset);
4337 linked_edits
4338 .entry(buffer.clone())
4339 .or_default()
4340 .push(start..end);
4341 }
4342 Some(linked_edits)
4343 }
4344
4345 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4346 let text: Arc<str> = text.into();
4347
4348 if self.read_only(cx) {
4349 return;
4350 }
4351
4352 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4353
4354 self.unfold_buffers_with_selections(cx);
4355
4356 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4357 let mut bracket_inserted = false;
4358 let mut edits = Vec::new();
4359 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4360 let mut new_selections = Vec::with_capacity(selections.len());
4361 let mut new_autoclose_regions = Vec::new();
4362 let snapshot = self.buffer.read(cx).read(cx);
4363 let mut clear_linked_edit_ranges = false;
4364 let mut all_selections_read_only = true;
4365 let mut has_adjacent_edits = false;
4366 let mut in_adjacent_group = false;
4367
4368 let mut regions = self
4369 .selections_with_autoclose_regions(selections, &snapshot)
4370 .peekable();
4371
4372 while let Some((selection, autoclose_region)) = regions.next() {
4373 if snapshot
4374 .point_to_buffer_point(selection.head())
4375 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4376 {
4377 continue;
4378 }
4379 if snapshot
4380 .point_to_buffer_point(selection.tail())
4381 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4382 {
4383 // note, ideally we'd clip the tail to the closest writeable region towards the head
4384 continue;
4385 }
4386 all_selections_read_only = false;
4387
4388 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4389 // Determine if the inserted text matches the opening or closing
4390 // bracket of any of this language's bracket pairs.
4391 let mut bracket_pair = None;
4392 let mut is_bracket_pair_start = false;
4393 let mut is_bracket_pair_end = false;
4394 if !text.is_empty() {
4395 let mut bracket_pair_matching_end = None;
4396 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4397 // and they are removing the character that triggered IME popup.
4398 for (pair, enabled) in scope.brackets() {
4399 if !pair.close && !pair.surround {
4400 continue;
4401 }
4402
4403 if enabled && pair.start.ends_with(text.as_ref()) {
4404 let prefix_len = pair.start.len() - text.len();
4405 let preceding_text_matches_prefix = prefix_len == 0
4406 || (selection.start.column >= (prefix_len as u32)
4407 && snapshot.contains_str_at(
4408 Point::new(
4409 selection.start.row,
4410 selection.start.column - (prefix_len as u32),
4411 ),
4412 &pair.start[..prefix_len],
4413 ));
4414 if preceding_text_matches_prefix {
4415 bracket_pair = Some(pair.clone());
4416 is_bracket_pair_start = true;
4417 break;
4418 }
4419 }
4420 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4421 {
4422 // take first bracket pair matching end, but don't break in case a later bracket
4423 // pair matches start
4424 bracket_pair_matching_end = Some(pair.clone());
4425 }
4426 }
4427 if let Some(end) = bracket_pair_matching_end
4428 && bracket_pair.is_none()
4429 {
4430 bracket_pair = Some(end);
4431 is_bracket_pair_end = true;
4432 }
4433 }
4434
4435 if let Some(bracket_pair) = bracket_pair {
4436 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4437 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4438 let auto_surround =
4439 self.use_auto_surround && snapshot_settings.use_auto_surround;
4440 if selection.is_empty() {
4441 if is_bracket_pair_start {
4442 // If the inserted text is a suffix of an opening bracket and the
4443 // selection is preceded by the rest of the opening bracket, then
4444 // insert the closing bracket.
4445 let following_text_allows_autoclose = snapshot
4446 .chars_at(selection.start)
4447 .next()
4448 .is_none_or(|c| scope.should_autoclose_before(c));
4449
4450 let preceding_text_allows_autoclose = selection.start.column == 0
4451 || snapshot
4452 .reversed_chars_at(selection.start)
4453 .next()
4454 .is_none_or(|c| {
4455 bracket_pair.start != bracket_pair.end
4456 || !snapshot
4457 .char_classifier_at(selection.start)
4458 .is_word(c)
4459 });
4460
4461 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4462 && bracket_pair.start.len() == 1
4463 {
4464 let target = bracket_pair.start.chars().next().unwrap();
4465 let mut byte_offset = 0u32;
4466 let current_line_count = snapshot
4467 .reversed_chars_at(selection.start)
4468 .take_while(|&c| c != '\n')
4469 .filter(|c| {
4470 byte_offset += c.len_utf8() as u32;
4471 if *c != target {
4472 return false;
4473 }
4474
4475 let point = Point::new(
4476 selection.start.row,
4477 selection.start.column.saturating_sub(byte_offset),
4478 );
4479
4480 let is_enabled = snapshot
4481 .language_scope_at(point)
4482 .and_then(|scope| {
4483 scope
4484 .brackets()
4485 .find(|(pair, _)| {
4486 pair.start == bracket_pair.start
4487 })
4488 .map(|(_, enabled)| enabled)
4489 })
4490 .unwrap_or(true);
4491
4492 let is_delimiter = snapshot
4493 .language_scope_at(Point::new(
4494 point.row,
4495 point.column + 1,
4496 ))
4497 .and_then(|scope| {
4498 scope
4499 .brackets()
4500 .find(|(pair, _)| {
4501 pair.start == bracket_pair.start
4502 })
4503 .map(|(_, enabled)| !enabled)
4504 })
4505 .unwrap_or(false);
4506
4507 is_enabled && !is_delimiter
4508 })
4509 .count();
4510 current_line_count % 2 == 1
4511 } else {
4512 false
4513 };
4514
4515 if autoclose
4516 && bracket_pair.close
4517 && following_text_allows_autoclose
4518 && preceding_text_allows_autoclose
4519 && !is_closing_quote
4520 {
4521 let anchor = snapshot.anchor_before(selection.end);
4522 new_selections.push((selection.map(|_| anchor), text.len()));
4523 new_autoclose_regions.push((
4524 anchor,
4525 text.len(),
4526 selection.id,
4527 bracket_pair.clone(),
4528 ));
4529 edits.push((
4530 selection.range(),
4531 format!("{}{}", text, bracket_pair.end).into(),
4532 ));
4533 bracket_inserted = true;
4534 continue;
4535 }
4536 }
4537
4538 if let Some(region) = autoclose_region {
4539 // If the selection is followed by an auto-inserted closing bracket,
4540 // then don't insert that closing bracket again; just move the selection
4541 // past the closing bracket.
4542 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4543 && text.as_ref() == region.pair.end.as_str()
4544 && snapshot.contains_str_at(region.range.end, text.as_ref());
4545 if should_skip {
4546 let anchor = snapshot.anchor_after(selection.end);
4547 new_selections
4548 .push((selection.map(|_| anchor), region.pair.end.len()));
4549 continue;
4550 }
4551 }
4552
4553 let always_treat_brackets_as_autoclosed = snapshot
4554 .language_settings_at(selection.start, cx)
4555 .always_treat_brackets_as_autoclosed;
4556 if always_treat_brackets_as_autoclosed
4557 && is_bracket_pair_end
4558 && snapshot.contains_str_at(selection.end, text.as_ref())
4559 {
4560 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4561 // and the inserted text is a closing bracket and the selection is followed
4562 // by the closing bracket then move the selection past the closing bracket.
4563 let anchor = snapshot.anchor_after(selection.end);
4564 new_selections.push((selection.map(|_| anchor), text.len()));
4565 continue;
4566 }
4567 }
4568 // If an opening bracket is 1 character long and is typed while
4569 // text is selected, then surround that text with the bracket pair.
4570 else if auto_surround
4571 && bracket_pair.surround
4572 && is_bracket_pair_start
4573 && bracket_pair.start.chars().count() == 1
4574 {
4575 edits.push((selection.start..selection.start, text.clone()));
4576 edits.push((
4577 selection.end..selection.end,
4578 bracket_pair.end.as_str().into(),
4579 ));
4580 bracket_inserted = true;
4581 new_selections.push((
4582 Selection {
4583 id: selection.id,
4584 start: snapshot.anchor_after(selection.start),
4585 end: snapshot.anchor_before(selection.end),
4586 reversed: selection.reversed,
4587 goal: selection.goal,
4588 },
4589 0,
4590 ));
4591 continue;
4592 }
4593 }
4594 }
4595
4596 if self.auto_replace_emoji_shortcode
4597 && selection.is_empty()
4598 && text.as_ref().ends_with(':')
4599 && let Some(possible_emoji_short_code) =
4600 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4601 && !possible_emoji_short_code.is_empty()
4602 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4603 {
4604 let emoji_shortcode_start = Point::new(
4605 selection.start.row,
4606 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4607 );
4608
4609 // Remove shortcode from buffer
4610 edits.push((
4611 emoji_shortcode_start..selection.start,
4612 "".to_string().into(),
4613 ));
4614 new_selections.push((
4615 Selection {
4616 id: selection.id,
4617 start: snapshot.anchor_after(emoji_shortcode_start),
4618 end: snapshot.anchor_before(selection.start),
4619 reversed: selection.reversed,
4620 goal: selection.goal,
4621 },
4622 0,
4623 ));
4624
4625 // Insert emoji
4626 let selection_start_anchor = snapshot.anchor_after(selection.start);
4627 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4628 edits.push((selection.start..selection.end, emoji.to_string().into()));
4629
4630 continue;
4631 }
4632
4633 let next_is_adjacent = regions
4634 .peek()
4635 .is_some_and(|(next, _)| selection.end == next.start);
4636
4637 // If not handling any auto-close operation, then just replace the selected
4638 // text with the given input and move the selection to the end of the
4639 // newly inserted text.
4640 let anchor = if in_adjacent_group || next_is_adjacent {
4641 // After edits the right bias would shift those anchor to the next visible fragment
4642 // but we want to resolve to the previous one
4643 snapshot.anchor_before(selection.end)
4644 } else {
4645 snapshot.anchor_after(selection.end)
4646 };
4647
4648 if !self.linked_edit_ranges.is_empty() {
4649 let start_anchor = snapshot.anchor_before(selection.start);
4650
4651 let is_word_char = text.chars().next().is_none_or(|char| {
4652 let classifier = snapshot
4653 .char_classifier_at(start_anchor.to_offset(&snapshot))
4654 .scope_context(Some(CharScopeContext::LinkedEdit));
4655 classifier.is_word(char)
4656 });
4657
4658 if is_word_char {
4659 if let Some(ranges) = self
4660 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4661 {
4662 for (buffer, edits) in ranges {
4663 linked_edits
4664 .entry(buffer.clone())
4665 .or_default()
4666 .extend(edits.into_iter().map(|range| (range, text.clone())));
4667 }
4668 }
4669 } else {
4670 clear_linked_edit_ranges = true;
4671 }
4672 }
4673
4674 new_selections.push((selection.map(|_| anchor), 0));
4675 edits.push((selection.start..selection.end, text.clone()));
4676
4677 has_adjacent_edits |= next_is_adjacent;
4678 in_adjacent_group = next_is_adjacent;
4679 }
4680
4681 if all_selections_read_only {
4682 return;
4683 }
4684
4685 drop(regions);
4686 drop(snapshot);
4687
4688 self.transact(window, cx, |this, window, cx| {
4689 if clear_linked_edit_ranges {
4690 this.linked_edit_ranges.clear();
4691 }
4692 let initial_buffer_versions =
4693 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4694
4695 this.buffer.update(cx, |buffer, cx| {
4696 if has_adjacent_edits {
4697 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4698 } else {
4699 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4700 }
4701 });
4702 for (buffer, edits) in linked_edits {
4703 buffer.update(cx, |buffer, cx| {
4704 let snapshot = buffer.snapshot();
4705 let edits = edits
4706 .into_iter()
4707 .map(|(range, text)| {
4708 use text::ToPoint as TP;
4709 let end_point = TP::to_point(&range.end, &snapshot);
4710 let start_point = TP::to_point(&range.start, &snapshot);
4711 (start_point..end_point, text)
4712 })
4713 .sorted_by_key(|(range, _)| range.start);
4714 buffer.edit(edits, None, cx);
4715 })
4716 }
4717 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4718 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4719 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4720 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4721 new_anchor_selections,
4722 &map,
4723 )
4724 .zip(new_selection_deltas)
4725 .map(|(selection, delta)| Selection {
4726 id: selection.id,
4727 start: selection.start + delta,
4728 end: selection.end + delta,
4729 reversed: selection.reversed,
4730 goal: SelectionGoal::None,
4731 })
4732 .collect::<Vec<_>>();
4733
4734 let mut i = 0;
4735 for (position, delta, selection_id, pair) in new_autoclose_regions {
4736 let position = position.to_offset(map.buffer_snapshot()) + delta;
4737 let start = map.buffer_snapshot().anchor_before(position);
4738 let end = map.buffer_snapshot().anchor_after(position);
4739 while let Some(existing_state) = this.autoclose_regions.get(i) {
4740 match existing_state
4741 .range
4742 .start
4743 .cmp(&start, map.buffer_snapshot())
4744 {
4745 Ordering::Less => i += 1,
4746 Ordering::Greater => break,
4747 Ordering::Equal => {
4748 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4749 Ordering::Less => i += 1,
4750 Ordering::Equal => break,
4751 Ordering::Greater => break,
4752 }
4753 }
4754 }
4755 }
4756 this.autoclose_regions.insert(
4757 i,
4758 AutocloseRegion {
4759 selection_id,
4760 range: start..end,
4761 pair,
4762 },
4763 );
4764 }
4765
4766 let had_active_edit_prediction = this.has_active_edit_prediction();
4767 this.change_selections(
4768 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4769 window,
4770 cx,
4771 |s| s.select(new_selections),
4772 );
4773
4774 if !bracket_inserted
4775 && let Some(on_type_format_task) =
4776 this.trigger_on_type_formatting(text.to_string(), window, cx)
4777 {
4778 on_type_format_task.detach_and_log_err(cx);
4779 }
4780
4781 let editor_settings = EditorSettings::get_global(cx);
4782 if bracket_inserted
4783 && (editor_settings.auto_signature_help
4784 || editor_settings.show_signature_help_after_edits)
4785 {
4786 this.show_signature_help(&ShowSignatureHelp, window, cx);
4787 }
4788
4789 let trigger_in_words =
4790 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4791 if this.hard_wrap.is_some() {
4792 let latest: Range<Point> = this.selections.newest(&map).range();
4793 if latest.is_empty()
4794 && this
4795 .buffer()
4796 .read(cx)
4797 .snapshot(cx)
4798 .line_len(MultiBufferRow(latest.start.row))
4799 == latest.start.column
4800 {
4801 this.rewrap_impl(
4802 RewrapOptions {
4803 override_language_settings: true,
4804 preserve_existing_whitespace: true,
4805 },
4806 cx,
4807 )
4808 }
4809 }
4810 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4811 refresh_linked_ranges(this, window, cx);
4812 this.refresh_edit_prediction(true, false, window, cx);
4813 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4814 });
4815 }
4816
4817 fn find_possible_emoji_shortcode_at_position(
4818 snapshot: &MultiBufferSnapshot,
4819 position: Point,
4820 ) -> Option<String> {
4821 let mut chars = Vec::new();
4822 let mut found_colon = false;
4823 for char in snapshot.reversed_chars_at(position).take(100) {
4824 // Found a possible emoji shortcode in the middle of the buffer
4825 if found_colon {
4826 if char.is_whitespace() {
4827 chars.reverse();
4828 return Some(chars.iter().collect());
4829 }
4830 // If the previous character is not a whitespace, we are in the middle of a word
4831 // and we only want to complete the shortcode if the word is made up of other emojis
4832 let mut containing_word = String::new();
4833 for ch in snapshot
4834 .reversed_chars_at(position)
4835 .skip(chars.len() + 1)
4836 .take(100)
4837 {
4838 if ch.is_whitespace() {
4839 break;
4840 }
4841 containing_word.push(ch);
4842 }
4843 let containing_word = containing_word.chars().rev().collect::<String>();
4844 if util::word_consists_of_emojis(containing_word.as_str()) {
4845 chars.reverse();
4846 return Some(chars.iter().collect());
4847 }
4848 }
4849
4850 if char.is_whitespace() || !char.is_ascii() {
4851 return None;
4852 }
4853 if char == ':' {
4854 found_colon = true;
4855 } else {
4856 chars.push(char);
4857 }
4858 }
4859 // Found a possible emoji shortcode at the beginning of the buffer
4860 chars.reverse();
4861 Some(chars.iter().collect())
4862 }
4863
4864 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4866 self.transact(window, cx, |this, window, cx| {
4867 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4868 let selections = this
4869 .selections
4870 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4871 let multi_buffer = this.buffer.read(cx);
4872 let buffer = multi_buffer.snapshot(cx);
4873 selections
4874 .iter()
4875 .map(|selection| {
4876 let start_point = selection.start.to_point(&buffer);
4877 let mut existing_indent =
4878 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4879 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4880 let start = selection.start;
4881 let end = selection.end;
4882 let selection_is_empty = start == end;
4883 let language_scope = buffer.language_scope_at(start);
4884 let (delimiter, newline_config) = if let Some(language) = &language_scope {
4885 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
4886 &buffer,
4887 start..end,
4888 language,
4889 )
4890 || NewlineConfig::insert_extra_newline_tree_sitter(
4891 &buffer,
4892 start..end,
4893 );
4894
4895 let mut newline_config = NewlineConfig::Newline {
4896 additional_indent: IndentSize::spaces(0),
4897 extra_line_additional_indent: if needs_extra_newline {
4898 Some(IndentSize::spaces(0))
4899 } else {
4900 None
4901 },
4902 prevent_auto_indent: false,
4903 };
4904
4905 let comment_delimiter = maybe!({
4906 if !selection_is_empty {
4907 return None;
4908 }
4909
4910 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4911 return None;
4912 }
4913
4914 return comment_delimiter_for_newline(
4915 &start_point,
4916 &buffer,
4917 language,
4918 );
4919 });
4920
4921 let doc_delimiter = maybe!({
4922 if !selection_is_empty {
4923 return None;
4924 }
4925
4926 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4927 return None;
4928 }
4929
4930 return documentation_delimiter_for_newline(
4931 &start_point,
4932 &buffer,
4933 language,
4934 &mut newline_config,
4935 );
4936 });
4937
4938 let list_delimiter = maybe!({
4939 if !selection_is_empty {
4940 return None;
4941 }
4942
4943 if !multi_buffer.language_settings(cx).extend_list_on_newline {
4944 return None;
4945 }
4946
4947 return list_delimiter_for_newline(
4948 &start_point,
4949 &buffer,
4950 language,
4951 &mut newline_config,
4952 );
4953 });
4954
4955 (
4956 comment_delimiter.or(doc_delimiter).or(list_delimiter),
4957 newline_config,
4958 )
4959 } else {
4960 (
4961 None,
4962 NewlineConfig::Newline {
4963 additional_indent: IndentSize::spaces(0),
4964 extra_line_additional_indent: None,
4965 prevent_auto_indent: false,
4966 },
4967 )
4968 };
4969
4970 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
4971 NewlineConfig::ClearCurrentLine => {
4972 let row_start =
4973 buffer.point_to_offset(Point::new(start_point.row, 0));
4974 (row_start, String::new(), false)
4975 }
4976 NewlineConfig::UnindentCurrentLine { continuation } => {
4977 let row_start =
4978 buffer.point_to_offset(Point::new(start_point.row, 0));
4979 let tab_size = buffer.language_settings_at(start, cx).tab_size;
4980 let tab_size_indent = IndentSize::spaces(tab_size.get());
4981 let reduced_indent =
4982 existing_indent.with_delta(Ordering::Less, tab_size_indent);
4983 let mut new_text = String::new();
4984 new_text.extend(reduced_indent.chars());
4985 new_text.push_str(continuation);
4986 (row_start, new_text, true)
4987 }
4988 NewlineConfig::Newline {
4989 additional_indent,
4990 extra_line_additional_indent,
4991 prevent_auto_indent,
4992 } => {
4993 let capacity_for_delimiter =
4994 delimiter.as_deref().map(str::len).unwrap_or_default();
4995 let extra_line_len = extra_line_additional_indent
4996 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
4997 .unwrap_or(0);
4998 let mut new_text = String::with_capacity(
4999 1 + capacity_for_delimiter
5000 + existing_indent.len as usize
5001 + additional_indent.len as usize
5002 + extra_line_len,
5003 );
5004 new_text.push('\n');
5005 new_text.extend(existing_indent.chars());
5006 new_text.extend(additional_indent.chars());
5007 if let Some(delimiter) = &delimiter {
5008 new_text.push_str(delimiter);
5009 }
5010 if let Some(extra_indent) = extra_line_additional_indent {
5011 new_text.push('\n');
5012 new_text.extend(existing_indent.chars());
5013 new_text.extend(extra_indent.chars());
5014 }
5015 (start, new_text, *prevent_auto_indent)
5016 }
5017 };
5018
5019 let anchor = buffer.anchor_after(end);
5020 let new_selection = selection.map(|_| anchor);
5021 (
5022 ((edit_start..end, new_text), prevent_auto_indent),
5023 (newline_config.has_extra_line(), new_selection),
5024 )
5025 })
5026 .unzip()
5027 };
5028
5029 let mut auto_indent_edits = Vec::new();
5030 let mut edits = Vec::new();
5031 for (edit, prevent_auto_indent) in edits_with_flags {
5032 if prevent_auto_indent {
5033 edits.push(edit);
5034 } else {
5035 auto_indent_edits.push(edit);
5036 }
5037 }
5038 if !edits.is_empty() {
5039 this.edit(edits, cx);
5040 }
5041 if !auto_indent_edits.is_empty() {
5042 this.edit_with_autoindent(auto_indent_edits, cx);
5043 }
5044
5045 let buffer = this.buffer.read(cx).snapshot(cx);
5046 let new_selections = selection_info
5047 .into_iter()
5048 .map(|(extra_newline_inserted, new_selection)| {
5049 let mut cursor = new_selection.end.to_point(&buffer);
5050 if extra_newline_inserted {
5051 cursor.row -= 1;
5052 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5053 }
5054 new_selection.map(|_| cursor)
5055 })
5056 .collect();
5057
5058 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5059 this.refresh_edit_prediction(true, false, window, cx);
5060 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5061 task.detach_and_log_err(cx);
5062 }
5063 });
5064 }
5065
5066 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5068
5069 let buffer = self.buffer.read(cx);
5070 let snapshot = buffer.snapshot(cx);
5071
5072 let mut edits = Vec::new();
5073 let mut rows = Vec::new();
5074
5075 for (rows_inserted, selection) in self
5076 .selections
5077 .all_adjusted(&self.display_snapshot(cx))
5078 .into_iter()
5079 .enumerate()
5080 {
5081 let cursor = selection.head();
5082 let row = cursor.row;
5083
5084 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5085
5086 let newline = "\n".to_string();
5087 edits.push((start_of_line..start_of_line, newline));
5088
5089 rows.push(row + rows_inserted as u32);
5090 }
5091
5092 self.transact(window, cx, |editor, window, cx| {
5093 editor.edit(edits, cx);
5094
5095 editor.change_selections(Default::default(), window, cx, |s| {
5096 let mut index = 0;
5097 s.move_cursors_with(|map, _, _| {
5098 let row = rows[index];
5099 index += 1;
5100
5101 let point = Point::new(row, 0);
5102 let boundary = map.next_line_boundary(point).1;
5103 let clipped = map.clip_point(boundary, Bias::Left);
5104
5105 (clipped, SelectionGoal::None)
5106 });
5107 });
5108
5109 let mut indent_edits = Vec::new();
5110 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5111 for row in rows {
5112 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5113 for (row, indent) in indents {
5114 if indent.len == 0 {
5115 continue;
5116 }
5117
5118 let text = match indent.kind {
5119 IndentKind::Space => " ".repeat(indent.len as usize),
5120 IndentKind::Tab => "\t".repeat(indent.len as usize),
5121 };
5122 let point = Point::new(row.0, 0);
5123 indent_edits.push((point..point, text));
5124 }
5125 }
5126 editor.edit(indent_edits, cx);
5127 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5128 format.detach_and_log_err(cx);
5129 }
5130 });
5131 }
5132
5133 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5134 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5135
5136 let buffer = self.buffer.read(cx);
5137 let snapshot = buffer.snapshot(cx);
5138
5139 let mut edits = Vec::new();
5140 let mut rows = Vec::new();
5141 let mut rows_inserted = 0;
5142
5143 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5144 let cursor = selection.head();
5145 let row = cursor.row;
5146
5147 let point = Point::new(row + 1, 0);
5148 let start_of_line = snapshot.clip_point(point, Bias::Left);
5149
5150 let newline = "\n".to_string();
5151 edits.push((start_of_line..start_of_line, newline));
5152
5153 rows_inserted += 1;
5154 rows.push(row + rows_inserted);
5155 }
5156
5157 self.transact(window, cx, |editor, window, cx| {
5158 editor.edit(edits, cx);
5159
5160 editor.change_selections(Default::default(), window, cx, |s| {
5161 let mut index = 0;
5162 s.move_cursors_with(|map, _, _| {
5163 let row = rows[index];
5164 index += 1;
5165
5166 let point = Point::new(row, 0);
5167 let boundary = map.next_line_boundary(point).1;
5168 let clipped = map.clip_point(boundary, Bias::Left);
5169
5170 (clipped, SelectionGoal::None)
5171 });
5172 });
5173
5174 let mut indent_edits = Vec::new();
5175 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5176 for row in rows {
5177 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5178 for (row, indent) in indents {
5179 if indent.len == 0 {
5180 continue;
5181 }
5182
5183 let text = match indent.kind {
5184 IndentKind::Space => " ".repeat(indent.len as usize),
5185 IndentKind::Tab => "\t".repeat(indent.len as usize),
5186 };
5187 let point = Point::new(row.0, 0);
5188 indent_edits.push((point..point, text));
5189 }
5190 }
5191 editor.edit(indent_edits, cx);
5192 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5193 format.detach_and_log_err(cx);
5194 }
5195 });
5196 }
5197
5198 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5199 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5200 original_indent_columns: Vec::new(),
5201 });
5202 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5203 }
5204
5205 fn insert_with_autoindent_mode(
5206 &mut self,
5207 text: &str,
5208 autoindent_mode: Option<AutoindentMode>,
5209 window: &mut Window,
5210 cx: &mut Context<Self>,
5211 ) {
5212 if self.read_only(cx) {
5213 return;
5214 }
5215
5216 let text: Arc<str> = text.into();
5217 self.transact(window, cx, |this, window, cx| {
5218 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5219 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5220 let anchors = {
5221 let snapshot = buffer.read(cx);
5222 old_selections
5223 .iter()
5224 .map(|s| {
5225 let anchor = snapshot.anchor_after(s.head());
5226 s.map(|_| anchor)
5227 })
5228 .collect::<Vec<_>>()
5229 };
5230 buffer.edit(
5231 old_selections
5232 .iter()
5233 .map(|s| (s.start..s.end, text.clone())),
5234 autoindent_mode,
5235 cx,
5236 );
5237 anchors
5238 });
5239
5240 this.change_selections(Default::default(), window, cx, |s| {
5241 s.select_anchors(selection_anchors);
5242 });
5243
5244 cx.notify();
5245 });
5246 }
5247
5248 fn trigger_completion_on_input(
5249 &mut self,
5250 text: &str,
5251 trigger_in_words: bool,
5252 window: &mut Window,
5253 cx: &mut Context<Self>,
5254 ) {
5255 let completions_source = self
5256 .context_menu
5257 .borrow()
5258 .as_ref()
5259 .and_then(|menu| match menu {
5260 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5261 CodeContextMenu::CodeActions(_) => None,
5262 });
5263
5264 match completions_source {
5265 Some(CompletionsMenuSource::Words { .. }) => {
5266 self.open_or_update_completions_menu(
5267 Some(CompletionsMenuSource::Words {
5268 ignore_threshold: false,
5269 }),
5270 None,
5271 trigger_in_words,
5272 window,
5273 cx,
5274 );
5275 }
5276 _ => self.open_or_update_completions_menu(
5277 None,
5278 Some(text.to_owned()).filter(|x| !x.is_empty()),
5279 true,
5280 window,
5281 cx,
5282 ),
5283 }
5284 }
5285
5286 /// If any empty selections is touching the start of its innermost containing autoclose
5287 /// region, expand it to select the brackets.
5288 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5289 let selections = self
5290 .selections
5291 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5292 let buffer = self.buffer.read(cx).read(cx);
5293 let new_selections = self
5294 .selections_with_autoclose_regions(selections, &buffer)
5295 .map(|(mut selection, region)| {
5296 if !selection.is_empty() {
5297 return selection;
5298 }
5299
5300 if let Some(region) = region {
5301 let mut range = region.range.to_offset(&buffer);
5302 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5303 range.start -= region.pair.start.len();
5304 if buffer.contains_str_at(range.start, ®ion.pair.start)
5305 && buffer.contains_str_at(range.end, ®ion.pair.end)
5306 {
5307 range.end += region.pair.end.len();
5308 selection.start = range.start;
5309 selection.end = range.end;
5310
5311 return selection;
5312 }
5313 }
5314 }
5315
5316 let always_treat_brackets_as_autoclosed = buffer
5317 .language_settings_at(selection.start, cx)
5318 .always_treat_brackets_as_autoclosed;
5319
5320 if !always_treat_brackets_as_autoclosed {
5321 return selection;
5322 }
5323
5324 if let Some(scope) = buffer.language_scope_at(selection.start) {
5325 for (pair, enabled) in scope.brackets() {
5326 if !enabled || !pair.close {
5327 continue;
5328 }
5329
5330 if buffer.contains_str_at(selection.start, &pair.end) {
5331 let pair_start_len = pair.start.len();
5332 if buffer.contains_str_at(
5333 selection.start.saturating_sub_usize(pair_start_len),
5334 &pair.start,
5335 ) {
5336 selection.start -= pair_start_len;
5337 selection.end += pair.end.len();
5338
5339 return selection;
5340 }
5341 }
5342 }
5343 }
5344
5345 selection
5346 })
5347 .collect();
5348
5349 drop(buffer);
5350 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5351 selections.select(new_selections)
5352 });
5353 }
5354
5355 /// Iterate the given selections, and for each one, find the smallest surrounding
5356 /// autoclose region. This uses the ordering of the selections and the autoclose
5357 /// regions to avoid repeated comparisons.
5358 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5359 &'a self,
5360 selections: impl IntoIterator<Item = Selection<D>>,
5361 buffer: &'a MultiBufferSnapshot,
5362 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5363 let mut i = 0;
5364 let mut regions = self.autoclose_regions.as_slice();
5365 selections.into_iter().map(move |selection| {
5366 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5367
5368 let mut enclosing = None;
5369 while let Some(pair_state) = regions.get(i) {
5370 if pair_state.range.end.to_offset(buffer) < range.start {
5371 regions = ®ions[i + 1..];
5372 i = 0;
5373 } else if pair_state.range.start.to_offset(buffer) > range.end {
5374 break;
5375 } else {
5376 if pair_state.selection_id == selection.id {
5377 enclosing = Some(pair_state);
5378 }
5379 i += 1;
5380 }
5381 }
5382
5383 (selection, enclosing)
5384 })
5385 }
5386
5387 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5388 fn invalidate_autoclose_regions(
5389 &mut self,
5390 mut selections: &[Selection<Anchor>],
5391 buffer: &MultiBufferSnapshot,
5392 ) {
5393 self.autoclose_regions.retain(|state| {
5394 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5395 return false;
5396 }
5397
5398 let mut i = 0;
5399 while let Some(selection) = selections.get(i) {
5400 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5401 selections = &selections[1..];
5402 continue;
5403 }
5404 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5405 break;
5406 }
5407 if selection.id == state.selection_id {
5408 return true;
5409 } else {
5410 i += 1;
5411 }
5412 }
5413 false
5414 });
5415 }
5416
5417 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5418 let offset = position.to_offset(buffer);
5419 let (word_range, kind) =
5420 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5421 if offset > word_range.start && kind == Some(CharKind::Word) {
5422 Some(
5423 buffer
5424 .text_for_range(word_range.start..offset)
5425 .collect::<String>(),
5426 )
5427 } else {
5428 None
5429 }
5430 }
5431
5432 pub fn visible_excerpts(
5433 &self,
5434 lsp_related_only: bool,
5435 cx: &mut Context<Editor>,
5436 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5437 let project = self.project().cloned();
5438 let multi_buffer = self.buffer().read(cx);
5439 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5440 let multi_buffer_visible_start = self
5441 .scroll_manager
5442 .anchor()
5443 .anchor
5444 .to_point(&multi_buffer_snapshot);
5445 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5446 multi_buffer_visible_start
5447 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5448 Bias::Left,
5449 );
5450 multi_buffer_snapshot
5451 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5452 .into_iter()
5453 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5454 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5455 if !lsp_related_only {
5456 return Some((
5457 excerpt_id,
5458 (
5459 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5460 buffer.version().clone(),
5461 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5462 ),
5463 ));
5464 }
5465
5466 let project = project.as_ref()?.read(cx);
5467 let buffer_file = project::File::from_dyn(buffer.file())?;
5468 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5469 let worktree_entry = buffer_worktree
5470 .read(cx)
5471 .entry_for_id(buffer_file.project_entry_id()?)?;
5472 if worktree_entry.is_ignored {
5473 None
5474 } else {
5475 Some((
5476 excerpt_id,
5477 (
5478 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5479 buffer.version().clone(),
5480 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5481 ),
5482 ))
5483 }
5484 })
5485 .collect()
5486 }
5487
5488 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5489 TextLayoutDetails {
5490 text_system: window.text_system().clone(),
5491 editor_style: self.style.clone().unwrap(),
5492 rem_size: window.rem_size(),
5493 scroll_anchor: self.scroll_manager.anchor(),
5494 visible_rows: self.visible_line_count(),
5495 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5496 }
5497 }
5498
5499 fn trigger_on_type_formatting(
5500 &self,
5501 input: String,
5502 window: &mut Window,
5503 cx: &mut Context<Self>,
5504 ) -> Option<Task<Result<()>>> {
5505 if input.chars().count() != 1 {
5506 return None;
5507 }
5508
5509 let project = self.project()?;
5510 let position = self.selections.newest_anchor().head();
5511 let (buffer, buffer_position) = self
5512 .buffer
5513 .read(cx)
5514 .text_anchor_for_position(position, cx)?;
5515
5516 let settings = language_settings::language_settings(
5517 buffer
5518 .read(cx)
5519 .language_at(buffer_position)
5520 .map(|l| l.name()),
5521 buffer.read(cx).file(),
5522 cx,
5523 );
5524 if !settings.use_on_type_format {
5525 return None;
5526 }
5527
5528 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5529 // hence we do LSP request & edit on host side only — add formats to host's history.
5530 let push_to_lsp_host_history = true;
5531 // If this is not the host, append its history with new edits.
5532 let push_to_client_history = project.read(cx).is_via_collab();
5533
5534 let on_type_formatting = project.update(cx, |project, cx| {
5535 project.on_type_format(
5536 buffer.clone(),
5537 buffer_position,
5538 input,
5539 push_to_lsp_host_history,
5540 cx,
5541 )
5542 });
5543 Some(cx.spawn_in(window, async move |editor, cx| {
5544 if let Some(transaction) = on_type_formatting.await? {
5545 if push_to_client_history {
5546 buffer.update(cx, |buffer, _| {
5547 buffer.push_transaction(transaction, Instant::now());
5548 buffer.finalize_last_transaction();
5549 });
5550 }
5551 editor.update(cx, |editor, cx| {
5552 editor.refresh_document_highlights(cx);
5553 })?;
5554 }
5555 Ok(())
5556 }))
5557 }
5558
5559 pub fn show_word_completions(
5560 &mut self,
5561 _: &ShowWordCompletions,
5562 window: &mut Window,
5563 cx: &mut Context<Self>,
5564 ) {
5565 self.open_or_update_completions_menu(
5566 Some(CompletionsMenuSource::Words {
5567 ignore_threshold: true,
5568 }),
5569 None,
5570 false,
5571 window,
5572 cx,
5573 );
5574 }
5575
5576 pub fn show_completions(
5577 &mut self,
5578 _: &ShowCompletions,
5579 window: &mut Window,
5580 cx: &mut Context<Self>,
5581 ) {
5582 self.open_or_update_completions_menu(None, None, false, window, cx);
5583 }
5584
5585 fn open_or_update_completions_menu(
5586 &mut self,
5587 requested_source: Option<CompletionsMenuSource>,
5588 trigger: Option<String>,
5589 trigger_in_words: bool,
5590 window: &mut Window,
5591 cx: &mut Context<Self>,
5592 ) {
5593 if self.pending_rename.is_some() {
5594 return;
5595 }
5596
5597 let completions_source = self
5598 .context_menu
5599 .borrow()
5600 .as_ref()
5601 .and_then(|menu| match menu {
5602 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5603 CodeContextMenu::CodeActions(_) => None,
5604 });
5605
5606 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5607
5608 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5609 // inserted and selected. To handle that case, the start of the selection is used so that
5610 // the menu starts with all choices.
5611 let position = self
5612 .selections
5613 .newest_anchor()
5614 .start
5615 .bias_right(&multibuffer_snapshot);
5616 if position.diff_base_anchor.is_some() {
5617 return;
5618 }
5619 let buffer_position = multibuffer_snapshot.anchor_before(position);
5620 let Some(buffer) = buffer_position
5621 .text_anchor
5622 .buffer_id
5623 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5624 else {
5625 return;
5626 };
5627 let buffer_snapshot = buffer.read(cx).snapshot();
5628
5629 let menu_is_open = matches!(
5630 self.context_menu.borrow().as_ref(),
5631 Some(CodeContextMenu::Completions(_))
5632 );
5633
5634 let language = buffer_snapshot
5635 .language_at(buffer_position.text_anchor)
5636 .map(|language| language.name());
5637
5638 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5639 let completion_settings = language_settings.completions.clone();
5640
5641 let show_completions_on_input = self
5642 .show_completions_on_input_override
5643 .unwrap_or(language_settings.show_completions_on_input);
5644 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5645 return;
5646 }
5647
5648 let query: Option<Arc<String>> =
5649 Self::completion_query(&multibuffer_snapshot, buffer_position)
5650 .map(|query| query.into());
5651
5652 drop(multibuffer_snapshot);
5653
5654 // Hide the current completions menu when query is empty. Without this, cached
5655 // completions from before the trigger char may be reused (#32774).
5656 if query.is_none() && menu_is_open {
5657 self.hide_context_menu(window, cx);
5658 }
5659
5660 let mut ignore_word_threshold = false;
5661 let provider = match requested_source {
5662 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5663 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5664 ignore_word_threshold = ignore_threshold;
5665 None
5666 }
5667 Some(CompletionsMenuSource::SnippetChoices)
5668 | Some(CompletionsMenuSource::SnippetsOnly) => {
5669 log::error!("bug: SnippetChoices requested_source is not handled");
5670 None
5671 }
5672 };
5673
5674 let sort_completions = provider
5675 .as_ref()
5676 .is_some_and(|provider| provider.sort_completions());
5677
5678 let filter_completions = provider
5679 .as_ref()
5680 .is_none_or(|provider| provider.filter_completions());
5681
5682 let was_snippets_only = matches!(
5683 completions_source,
5684 Some(CompletionsMenuSource::SnippetsOnly)
5685 );
5686
5687 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5688 if filter_completions {
5689 menu.filter(
5690 query.clone().unwrap_or_default(),
5691 buffer_position.text_anchor,
5692 &buffer,
5693 provider.clone(),
5694 window,
5695 cx,
5696 );
5697 }
5698 // When `is_incomplete` is false, no need to re-query completions when the current query
5699 // is a suffix of the initial query.
5700 let was_complete = !menu.is_incomplete;
5701 if was_complete && !was_snippets_only {
5702 // If the new query is a suffix of the old query (typing more characters) and
5703 // the previous result was complete, the existing completions can be filtered.
5704 //
5705 // Note that snippet completions are always complete.
5706 let query_matches = match (&menu.initial_query, &query) {
5707 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5708 (None, _) => true,
5709 _ => false,
5710 };
5711 if query_matches {
5712 let position_matches = if menu.initial_position == position {
5713 true
5714 } else {
5715 let snapshot = self.buffer.read(cx).read(cx);
5716 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5717 };
5718 if position_matches {
5719 return;
5720 }
5721 }
5722 }
5723 };
5724
5725 let Anchor {
5726 excerpt_id: buffer_excerpt_id,
5727 text_anchor: buffer_position,
5728 ..
5729 } = buffer_position;
5730
5731 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5732 buffer_snapshot.surrounding_word(buffer_position, None)
5733 {
5734 let word_to_exclude = buffer_snapshot
5735 .text_for_range(word_range.clone())
5736 .collect::<String>();
5737 (
5738 buffer_snapshot.anchor_before(word_range.start)
5739 ..buffer_snapshot.anchor_after(buffer_position),
5740 Some(word_to_exclude),
5741 )
5742 } else {
5743 (buffer_position..buffer_position, None)
5744 };
5745
5746 let show_completion_documentation = buffer_snapshot
5747 .settings_at(buffer_position, cx)
5748 .show_completion_documentation;
5749
5750 // The document can be large, so stay in reasonable bounds when searching for words,
5751 // otherwise completion pop-up might be slow to appear.
5752 const WORD_LOOKUP_ROWS: u32 = 5_000;
5753 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5754 let min_word_search = buffer_snapshot.clip_point(
5755 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5756 Bias::Left,
5757 );
5758 let max_word_search = buffer_snapshot.clip_point(
5759 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5760 Bias::Right,
5761 );
5762 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5763 ..buffer_snapshot.point_to_offset(max_word_search);
5764
5765 let skip_digits = query
5766 .as_ref()
5767 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5768
5769 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5770 trigger.as_ref().is_none_or(|trigger| {
5771 provider.is_completion_trigger(
5772 &buffer,
5773 position.text_anchor,
5774 trigger,
5775 trigger_in_words,
5776 cx,
5777 )
5778 })
5779 });
5780
5781 let provider_responses = if let Some(provider) = &provider
5782 && load_provider_completions
5783 {
5784 let trigger_character =
5785 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5786 let completion_context = CompletionContext {
5787 trigger_kind: match &trigger_character {
5788 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5789 None => CompletionTriggerKind::INVOKED,
5790 },
5791 trigger_character,
5792 };
5793
5794 provider.completions(
5795 buffer_excerpt_id,
5796 &buffer,
5797 buffer_position,
5798 completion_context,
5799 window,
5800 cx,
5801 )
5802 } else {
5803 Task::ready(Ok(Vec::new()))
5804 };
5805
5806 let load_word_completions = if !self.word_completions_enabled {
5807 false
5808 } else if requested_source
5809 == Some(CompletionsMenuSource::Words {
5810 ignore_threshold: true,
5811 })
5812 {
5813 true
5814 } else {
5815 load_provider_completions
5816 && completion_settings.words != WordsCompletionMode::Disabled
5817 && (ignore_word_threshold || {
5818 let words_min_length = completion_settings.words_min_length;
5819 // check whether word has at least `words_min_length` characters
5820 let query_chars = query.iter().flat_map(|q| q.chars());
5821 query_chars.take(words_min_length).count() == words_min_length
5822 })
5823 };
5824
5825 let mut words = if load_word_completions {
5826 cx.background_spawn({
5827 let buffer_snapshot = buffer_snapshot.clone();
5828 async move {
5829 buffer_snapshot.words_in_range(WordsQuery {
5830 fuzzy_contents: None,
5831 range: word_search_range,
5832 skip_digits,
5833 })
5834 }
5835 })
5836 } else {
5837 Task::ready(BTreeMap::default())
5838 };
5839
5840 let snippets = if let Some(provider) = &provider
5841 && provider.show_snippets()
5842 && let Some(project) = self.project()
5843 {
5844 let char_classifier = buffer_snapshot
5845 .char_classifier_at(buffer_position)
5846 .scope_context(Some(CharScopeContext::Completion));
5847 project.update(cx, |project, cx| {
5848 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5849 })
5850 } else {
5851 Task::ready(Ok(CompletionResponse {
5852 completions: Vec::new(),
5853 display_options: Default::default(),
5854 is_incomplete: false,
5855 }))
5856 };
5857
5858 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5859
5860 let id = post_inc(&mut self.next_completion_id);
5861 let task = cx.spawn_in(window, async move |editor, cx| {
5862 let Ok(()) = editor.update(cx, |this, _| {
5863 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5864 }) else {
5865 return;
5866 };
5867
5868 // TODO: Ideally completions from different sources would be selectively re-queried, so
5869 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5870 let mut completions = Vec::new();
5871 let mut is_incomplete = false;
5872 let mut display_options: Option<CompletionDisplayOptions> = None;
5873 if let Some(provider_responses) = provider_responses.await.log_err()
5874 && !provider_responses.is_empty()
5875 {
5876 for response in provider_responses {
5877 completions.extend(response.completions);
5878 is_incomplete = is_incomplete || response.is_incomplete;
5879 match display_options.as_mut() {
5880 None => {
5881 display_options = Some(response.display_options);
5882 }
5883 Some(options) => options.merge(&response.display_options),
5884 }
5885 }
5886 if completion_settings.words == WordsCompletionMode::Fallback {
5887 words = Task::ready(BTreeMap::default());
5888 }
5889 }
5890 let display_options = display_options.unwrap_or_default();
5891
5892 let mut words = words.await;
5893 if let Some(word_to_exclude) = &word_to_exclude {
5894 words.remove(word_to_exclude);
5895 }
5896 for lsp_completion in &completions {
5897 words.remove(&lsp_completion.new_text);
5898 }
5899 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5900 replace_range: word_replace_range.clone(),
5901 new_text: word.clone(),
5902 label: CodeLabel::plain(word, None),
5903 match_start: None,
5904 snippet_deduplication_key: None,
5905 icon_path: None,
5906 documentation: None,
5907 source: CompletionSource::BufferWord {
5908 word_range,
5909 resolved: false,
5910 },
5911 insert_text_mode: Some(InsertTextMode::AS_IS),
5912 confirm: None,
5913 }));
5914
5915 completions.extend(
5916 snippets
5917 .await
5918 .into_iter()
5919 .flat_map(|response| response.completions),
5920 );
5921
5922 let menu = if completions.is_empty() {
5923 None
5924 } else {
5925 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5926 let languages = editor
5927 .workspace
5928 .as_ref()
5929 .and_then(|(workspace, _)| workspace.upgrade())
5930 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5931 let menu = CompletionsMenu::new(
5932 id,
5933 requested_source.unwrap_or(if load_provider_completions {
5934 CompletionsMenuSource::Normal
5935 } else {
5936 CompletionsMenuSource::SnippetsOnly
5937 }),
5938 sort_completions,
5939 show_completion_documentation,
5940 position,
5941 query.clone(),
5942 is_incomplete,
5943 buffer.clone(),
5944 completions.into(),
5945 editor
5946 .context_menu()
5947 .borrow_mut()
5948 .as_ref()
5949 .map(|menu| menu.primary_scroll_handle()),
5950 display_options,
5951 snippet_sort_order,
5952 languages,
5953 language,
5954 cx,
5955 );
5956
5957 let query = if filter_completions { query } else { None };
5958 let matches_task = menu.do_async_filtering(
5959 query.unwrap_or_default(),
5960 buffer_position,
5961 &buffer,
5962 cx,
5963 );
5964 (menu, matches_task)
5965 }) else {
5966 return;
5967 };
5968
5969 let matches = matches_task.await;
5970
5971 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5972 // Newer menu already set, so exit.
5973 if let Some(CodeContextMenu::Completions(prev_menu)) =
5974 editor.context_menu.borrow().as_ref()
5975 && prev_menu.id > id
5976 {
5977 return;
5978 };
5979
5980 // Only valid to take prev_menu because either the new menu is immediately set
5981 // below, or the menu is hidden.
5982 if let Some(CodeContextMenu::Completions(prev_menu)) =
5983 editor.context_menu.borrow_mut().take()
5984 {
5985 let position_matches =
5986 if prev_menu.initial_position == menu.initial_position {
5987 true
5988 } else {
5989 let snapshot = editor.buffer.read(cx).read(cx);
5990 prev_menu.initial_position.to_offset(&snapshot)
5991 == menu.initial_position.to_offset(&snapshot)
5992 };
5993 if position_matches {
5994 // Preserve markdown cache before `set_filter_results` because it will
5995 // try to populate the documentation cache.
5996 menu.preserve_markdown_cache(prev_menu);
5997 }
5998 };
5999
6000 menu.set_filter_results(matches, provider, window, cx);
6001 }) else {
6002 return;
6003 };
6004
6005 menu.visible().then_some(menu)
6006 };
6007
6008 editor
6009 .update_in(cx, |editor, window, cx| {
6010 if editor.focus_handle.is_focused(window)
6011 && let Some(menu) = menu
6012 {
6013 *editor.context_menu.borrow_mut() =
6014 Some(CodeContextMenu::Completions(menu));
6015
6016 crate::hover_popover::hide_hover(editor, cx);
6017 if editor.show_edit_predictions_in_menu() {
6018 editor.update_visible_edit_prediction(window, cx);
6019 } else {
6020 editor.discard_edit_prediction(false, cx);
6021 }
6022
6023 cx.notify();
6024 return;
6025 }
6026
6027 if editor.completion_tasks.len() <= 1 {
6028 // If there are no more completion tasks and the last menu was empty, we should hide it.
6029 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6030 // If it was already hidden and we don't show edit predictions in the menu,
6031 // we should also show the edit prediction when available.
6032 if was_hidden && editor.show_edit_predictions_in_menu() {
6033 editor.update_visible_edit_prediction(window, cx);
6034 }
6035 }
6036 })
6037 .ok();
6038 });
6039
6040 self.completion_tasks.push((id, task));
6041 }
6042
6043 #[cfg(feature = "test-support")]
6044 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6045 let menu = self.context_menu.borrow();
6046 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6047 let completions = menu.completions.borrow();
6048 Some(completions.to_vec())
6049 } else {
6050 None
6051 }
6052 }
6053
6054 pub fn with_completions_menu_matching_id<R>(
6055 &self,
6056 id: CompletionId,
6057 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6058 ) -> R {
6059 let mut context_menu = self.context_menu.borrow_mut();
6060 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6061 return f(None);
6062 };
6063 if completions_menu.id != id {
6064 return f(None);
6065 }
6066 f(Some(completions_menu))
6067 }
6068
6069 pub fn confirm_completion(
6070 &mut self,
6071 action: &ConfirmCompletion,
6072 window: &mut Window,
6073 cx: &mut Context<Self>,
6074 ) -> Option<Task<Result<()>>> {
6075 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6076 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6077 }
6078
6079 pub fn confirm_completion_insert(
6080 &mut self,
6081 _: &ConfirmCompletionInsert,
6082 window: &mut Window,
6083 cx: &mut Context<Self>,
6084 ) -> Option<Task<Result<()>>> {
6085 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6086 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6087 }
6088
6089 pub fn confirm_completion_replace(
6090 &mut self,
6091 _: &ConfirmCompletionReplace,
6092 window: &mut Window,
6093 cx: &mut Context<Self>,
6094 ) -> Option<Task<Result<()>>> {
6095 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6096 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6097 }
6098
6099 pub fn compose_completion(
6100 &mut self,
6101 action: &ComposeCompletion,
6102 window: &mut Window,
6103 cx: &mut Context<Self>,
6104 ) -> Option<Task<Result<()>>> {
6105 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6106 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6107 }
6108
6109 fn do_completion(
6110 &mut self,
6111 item_ix: Option<usize>,
6112 intent: CompletionIntent,
6113 window: &mut Window,
6114 cx: &mut Context<Editor>,
6115 ) -> Option<Task<Result<()>>> {
6116 use language::ToOffset as _;
6117
6118 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6119 else {
6120 return None;
6121 };
6122
6123 let candidate_id = {
6124 let entries = completions_menu.entries.borrow();
6125 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6126 if self.show_edit_predictions_in_menu() {
6127 self.discard_edit_prediction(true, cx);
6128 }
6129 mat.candidate_id
6130 };
6131
6132 let completion = completions_menu
6133 .completions
6134 .borrow()
6135 .get(candidate_id)?
6136 .clone();
6137 cx.stop_propagation();
6138
6139 let buffer_handle = completions_menu.buffer.clone();
6140
6141 let CompletionEdit {
6142 new_text,
6143 snippet,
6144 replace_range,
6145 } = process_completion_for_edit(
6146 &completion,
6147 intent,
6148 &buffer_handle,
6149 &completions_menu.initial_position.text_anchor,
6150 cx,
6151 );
6152
6153 let buffer = buffer_handle.read(cx);
6154 let snapshot = self.buffer.read(cx).snapshot(cx);
6155 let newest_anchor = self.selections.newest_anchor();
6156 let replace_range_multibuffer = {
6157 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6158 excerpt.map_range_from_buffer(replace_range.clone())
6159 };
6160 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6161 return None;
6162 }
6163
6164 let old_text = buffer
6165 .text_for_range(replace_range.clone())
6166 .collect::<String>();
6167 let lookbehind = newest_anchor
6168 .start
6169 .text_anchor
6170 .to_offset(buffer)
6171 .saturating_sub(replace_range.start.0);
6172 let lookahead = replace_range
6173 .end
6174 .0
6175 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6176 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6177 let suffix = &old_text[lookbehind.min(old_text.len())..];
6178
6179 let selections = self
6180 .selections
6181 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6182 let mut ranges = Vec::new();
6183 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6184
6185 for selection in &selections {
6186 let range = if selection.id == newest_anchor.id {
6187 replace_range_multibuffer.clone()
6188 } else {
6189 let mut range = selection.range();
6190
6191 // if prefix is present, don't duplicate it
6192 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6193 range.start = range.start.saturating_sub_usize(lookbehind);
6194
6195 // if suffix is also present, mimic the newest cursor and replace it
6196 if selection.id != newest_anchor.id
6197 && snapshot.contains_str_at(range.end, suffix)
6198 {
6199 range.end += lookahead;
6200 }
6201 }
6202 range
6203 };
6204
6205 ranges.push(range.clone());
6206
6207 if !self.linked_edit_ranges.is_empty() {
6208 let start_anchor = snapshot.anchor_before(range.start);
6209 let end_anchor = snapshot.anchor_after(range.end);
6210 if let Some(ranges) = self
6211 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6212 {
6213 for (buffer, edits) in ranges {
6214 linked_edits
6215 .entry(buffer.clone())
6216 .or_default()
6217 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6218 }
6219 }
6220 }
6221 }
6222
6223 let common_prefix_len = old_text
6224 .chars()
6225 .zip(new_text.chars())
6226 .take_while(|(a, b)| a == b)
6227 .map(|(a, _)| a.len_utf8())
6228 .sum::<usize>();
6229
6230 cx.emit(EditorEvent::InputHandled {
6231 utf16_range_to_replace: None,
6232 text: new_text[common_prefix_len..].into(),
6233 });
6234
6235 self.transact(window, cx, |editor, window, cx| {
6236 if let Some(mut snippet) = snippet {
6237 snippet.text = new_text.to_string();
6238 editor
6239 .insert_snippet(&ranges, snippet, window, cx)
6240 .log_err();
6241 } else {
6242 editor.buffer.update(cx, |multi_buffer, cx| {
6243 let auto_indent = match completion.insert_text_mode {
6244 Some(InsertTextMode::AS_IS) => None,
6245 _ => editor.autoindent_mode.clone(),
6246 };
6247 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6248 multi_buffer.edit(edits, auto_indent, cx);
6249 });
6250 }
6251 for (buffer, edits) in linked_edits {
6252 buffer.update(cx, |buffer, cx| {
6253 let snapshot = buffer.snapshot();
6254 let edits = edits
6255 .into_iter()
6256 .map(|(range, text)| {
6257 use text::ToPoint as TP;
6258 let end_point = TP::to_point(&range.end, &snapshot);
6259 let start_point = TP::to_point(&range.start, &snapshot);
6260 (start_point..end_point, text)
6261 })
6262 .sorted_by_key(|(range, _)| range.start);
6263 buffer.edit(edits, None, cx);
6264 })
6265 }
6266
6267 editor.refresh_edit_prediction(true, false, window, cx);
6268 });
6269 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6270
6271 let show_new_completions_on_confirm = completion
6272 .confirm
6273 .as_ref()
6274 .is_some_and(|confirm| confirm(intent, window, cx));
6275 if show_new_completions_on_confirm {
6276 self.open_or_update_completions_menu(None, None, false, window, cx);
6277 }
6278
6279 let provider = self.completion_provider.as_ref()?;
6280
6281 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6282 let command = lsp_store.as_ref().and_then(|lsp_store| {
6283 let CompletionSource::Lsp {
6284 lsp_completion,
6285 server_id,
6286 ..
6287 } = &completion.source
6288 else {
6289 return None;
6290 };
6291 let lsp_command = lsp_completion.command.as_ref()?;
6292 let available_commands = lsp_store
6293 .read(cx)
6294 .lsp_server_capabilities
6295 .get(server_id)
6296 .and_then(|server_capabilities| {
6297 server_capabilities
6298 .execute_command_provider
6299 .as_ref()
6300 .map(|options| options.commands.as_slice())
6301 })?;
6302 if available_commands.contains(&lsp_command.command) {
6303 Some(CodeAction {
6304 server_id: *server_id,
6305 range: language::Anchor::MIN..language::Anchor::MIN,
6306 lsp_action: LspAction::Command(lsp_command.clone()),
6307 resolved: false,
6308 })
6309 } else {
6310 None
6311 }
6312 });
6313
6314 drop(completion);
6315 let apply_edits = provider.apply_additional_edits_for_completion(
6316 buffer_handle.clone(),
6317 completions_menu.completions.clone(),
6318 candidate_id,
6319 true,
6320 cx,
6321 );
6322
6323 let editor_settings = EditorSettings::get_global(cx);
6324 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6325 // After the code completion is finished, users often want to know what signatures are needed.
6326 // so we should automatically call signature_help
6327 self.show_signature_help(&ShowSignatureHelp, window, cx);
6328 }
6329
6330 Some(cx.spawn_in(window, async move |editor, cx| {
6331 apply_edits.await?;
6332
6333 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6334 let title = command.lsp_action.title().to_owned();
6335 let project_transaction = lsp_store
6336 .update(cx, |lsp_store, cx| {
6337 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6338 })
6339 .await
6340 .context("applying post-completion command")?;
6341 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6342 Self::open_project_transaction(
6343 &editor,
6344 workspace.downgrade(),
6345 project_transaction,
6346 title,
6347 cx,
6348 )
6349 .await?;
6350 }
6351 }
6352
6353 Ok(())
6354 }))
6355 }
6356
6357 pub fn toggle_code_actions(
6358 &mut self,
6359 action: &ToggleCodeActions,
6360 window: &mut Window,
6361 cx: &mut Context<Self>,
6362 ) {
6363 let quick_launch = action.quick_launch;
6364 let mut context_menu = self.context_menu.borrow_mut();
6365 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6366 if code_actions.deployed_from == action.deployed_from {
6367 // Toggle if we're selecting the same one
6368 *context_menu = None;
6369 cx.notify();
6370 return;
6371 } else {
6372 // Otherwise, clear it and start a new one
6373 *context_menu = None;
6374 cx.notify();
6375 }
6376 }
6377 drop(context_menu);
6378 let snapshot = self.snapshot(window, cx);
6379 let deployed_from = action.deployed_from.clone();
6380 let action = action.clone();
6381 self.completion_tasks.clear();
6382 self.discard_edit_prediction(false, cx);
6383
6384 let multibuffer_point = match &action.deployed_from {
6385 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6386 DisplayPoint::new(*row, 0).to_point(&snapshot)
6387 }
6388 _ => self
6389 .selections
6390 .newest::<Point>(&snapshot.display_snapshot)
6391 .head(),
6392 };
6393 let Some((buffer, buffer_row)) = snapshot
6394 .buffer_snapshot()
6395 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6396 .and_then(|(buffer_snapshot, range)| {
6397 self.buffer()
6398 .read(cx)
6399 .buffer(buffer_snapshot.remote_id())
6400 .map(|buffer| (buffer, range.start.row))
6401 })
6402 else {
6403 return;
6404 };
6405 let buffer_id = buffer.read(cx).remote_id();
6406 let tasks = self
6407 .tasks
6408 .get(&(buffer_id, buffer_row))
6409 .map(|t| Arc::new(t.to_owned()));
6410
6411 if !self.focus_handle.is_focused(window) {
6412 return;
6413 }
6414 let project = self.project.clone();
6415
6416 let code_actions_task = match deployed_from {
6417 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6418 _ => self.code_actions(buffer_row, window, cx),
6419 };
6420
6421 let runnable_task = match deployed_from {
6422 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6423 _ => {
6424 let mut task_context_task = Task::ready(None);
6425 if let Some(tasks) = &tasks
6426 && let Some(project) = project
6427 {
6428 task_context_task =
6429 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6430 }
6431
6432 cx.spawn_in(window, {
6433 let buffer = buffer.clone();
6434 async move |editor, cx| {
6435 let task_context = task_context_task.await;
6436
6437 let resolved_tasks =
6438 tasks
6439 .zip(task_context.clone())
6440 .map(|(tasks, task_context)| ResolvedTasks {
6441 templates: tasks.resolve(&task_context).collect(),
6442 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6443 multibuffer_point.row,
6444 tasks.column,
6445 )),
6446 });
6447 let debug_scenarios = editor
6448 .update(cx, |editor, cx| {
6449 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6450 })?
6451 .await;
6452 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6453 }
6454 })
6455 }
6456 };
6457
6458 cx.spawn_in(window, async move |editor, cx| {
6459 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6460 let code_actions = code_actions_task.await;
6461 let spawn_straight_away = quick_launch
6462 && resolved_tasks
6463 .as_ref()
6464 .is_some_and(|tasks| tasks.templates.len() == 1)
6465 && code_actions
6466 .as_ref()
6467 .is_none_or(|actions| actions.is_empty())
6468 && debug_scenarios.is_empty();
6469
6470 editor.update_in(cx, |editor, window, cx| {
6471 crate::hover_popover::hide_hover(editor, cx);
6472 let actions = CodeActionContents::new(
6473 resolved_tasks,
6474 code_actions,
6475 debug_scenarios,
6476 task_context.unwrap_or_default(),
6477 );
6478
6479 // Don't show the menu if there are no actions available
6480 if actions.is_empty() {
6481 cx.notify();
6482 return Task::ready(Ok(()));
6483 }
6484
6485 *editor.context_menu.borrow_mut() =
6486 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6487 buffer,
6488 actions,
6489 selected_item: Default::default(),
6490 scroll_handle: UniformListScrollHandle::default(),
6491 deployed_from,
6492 }));
6493 cx.notify();
6494 if spawn_straight_away
6495 && let Some(task) = editor.confirm_code_action(
6496 &ConfirmCodeAction { item_ix: Some(0) },
6497 window,
6498 cx,
6499 )
6500 {
6501 return task;
6502 }
6503
6504 Task::ready(Ok(()))
6505 })
6506 })
6507 .detach_and_log_err(cx);
6508 }
6509
6510 fn debug_scenarios(
6511 &mut self,
6512 resolved_tasks: &Option<ResolvedTasks>,
6513 buffer: &Entity<Buffer>,
6514 cx: &mut App,
6515 ) -> Task<Vec<task::DebugScenario>> {
6516 maybe!({
6517 let project = self.project()?;
6518 let dap_store = project.read(cx).dap_store();
6519 let mut scenarios = vec![];
6520 let resolved_tasks = resolved_tasks.as_ref()?;
6521 let buffer = buffer.read(cx);
6522 let language = buffer.language()?;
6523 let file = buffer.file();
6524 let debug_adapter = language_settings(language.name().into(), file, cx)
6525 .debuggers
6526 .first()
6527 .map(SharedString::from)
6528 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6529
6530 dap_store.update(cx, |dap_store, cx| {
6531 for (_, task) in &resolved_tasks.templates {
6532 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6533 task.original_task().clone(),
6534 debug_adapter.clone().into(),
6535 task.display_label().to_owned().into(),
6536 cx,
6537 );
6538 scenarios.push(maybe_scenario);
6539 }
6540 });
6541 Some(cx.background_spawn(async move {
6542 futures::future::join_all(scenarios)
6543 .await
6544 .into_iter()
6545 .flatten()
6546 .collect::<Vec<_>>()
6547 }))
6548 })
6549 .unwrap_or_else(|| Task::ready(vec![]))
6550 }
6551
6552 fn code_actions(
6553 &mut self,
6554 buffer_row: u32,
6555 window: &mut Window,
6556 cx: &mut Context<Self>,
6557 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6558 let mut task = self.code_actions_task.take();
6559 cx.spawn_in(window, async move |editor, cx| {
6560 while let Some(prev_task) = task {
6561 prev_task.await.log_err();
6562 task = editor
6563 .update(cx, |this, _| this.code_actions_task.take())
6564 .ok()?;
6565 }
6566
6567 editor
6568 .update(cx, |editor, cx| {
6569 editor
6570 .available_code_actions
6571 .clone()
6572 .and_then(|(location, code_actions)| {
6573 let snapshot = location.buffer.read(cx).snapshot();
6574 let point_range = location.range.to_point(&snapshot);
6575 let point_range = point_range.start.row..=point_range.end.row;
6576 if point_range.contains(&buffer_row) {
6577 Some(code_actions)
6578 } else {
6579 None
6580 }
6581 })
6582 })
6583 .ok()
6584 .flatten()
6585 })
6586 }
6587
6588 pub fn confirm_code_action(
6589 &mut self,
6590 action: &ConfirmCodeAction,
6591 window: &mut Window,
6592 cx: &mut Context<Self>,
6593 ) -> Option<Task<Result<()>>> {
6594 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6595
6596 let actions_menu =
6597 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6598 menu
6599 } else {
6600 return None;
6601 };
6602
6603 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6604 let action = actions_menu.actions.get(action_ix)?;
6605 let title = action.label();
6606 let buffer = actions_menu.buffer;
6607 let workspace = self.workspace()?;
6608
6609 match action {
6610 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6611 workspace.update(cx, |workspace, cx| {
6612 workspace.schedule_resolved_task(
6613 task_source_kind,
6614 resolved_task,
6615 false,
6616 window,
6617 cx,
6618 );
6619
6620 Some(Task::ready(Ok(())))
6621 })
6622 }
6623 CodeActionsItem::CodeAction {
6624 excerpt_id,
6625 action,
6626 provider,
6627 } => {
6628 let apply_code_action =
6629 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6630 let workspace = workspace.downgrade();
6631 Some(cx.spawn_in(window, async move |editor, cx| {
6632 let project_transaction = apply_code_action.await?;
6633 Self::open_project_transaction(
6634 &editor,
6635 workspace,
6636 project_transaction,
6637 title,
6638 cx,
6639 )
6640 .await
6641 }))
6642 }
6643 CodeActionsItem::DebugScenario(scenario) => {
6644 let context = actions_menu.actions.context;
6645
6646 workspace.update(cx, |workspace, cx| {
6647 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6648 workspace.start_debug_session(
6649 scenario,
6650 context,
6651 Some(buffer),
6652 None,
6653 window,
6654 cx,
6655 );
6656 });
6657 Some(Task::ready(Ok(())))
6658 }
6659 }
6660 }
6661
6662 fn open_transaction_for_hidden_buffers(
6663 workspace: Entity<Workspace>,
6664 transaction: ProjectTransaction,
6665 title: String,
6666 window: &mut Window,
6667 cx: &mut Context<Self>,
6668 ) {
6669 if transaction.0.is_empty() {
6670 return;
6671 }
6672
6673 let edited_buffers_already_open = {
6674 let other_editors: Vec<Entity<Editor>> = workspace
6675 .read(cx)
6676 .panes()
6677 .iter()
6678 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6679 .filter(|editor| editor.entity_id() != cx.entity_id())
6680 .collect();
6681
6682 transaction.0.keys().all(|buffer| {
6683 other_editors.iter().any(|editor| {
6684 let multi_buffer = editor.read(cx).buffer();
6685 multi_buffer.read(cx).is_singleton()
6686 && multi_buffer
6687 .read(cx)
6688 .as_singleton()
6689 .map_or(false, |singleton| {
6690 singleton.entity_id() == buffer.entity_id()
6691 })
6692 })
6693 })
6694 };
6695 if !edited_buffers_already_open {
6696 let workspace = workspace.downgrade();
6697 cx.defer_in(window, move |_, window, cx| {
6698 cx.spawn_in(window, async move |editor, cx| {
6699 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6700 .await
6701 .ok()
6702 })
6703 .detach();
6704 });
6705 }
6706 }
6707
6708 pub async fn open_project_transaction(
6709 editor: &WeakEntity<Editor>,
6710 workspace: WeakEntity<Workspace>,
6711 transaction: ProjectTransaction,
6712 title: String,
6713 cx: &mut AsyncWindowContext,
6714 ) -> Result<()> {
6715 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6716 cx.update(|_, cx| {
6717 entries.sort_unstable_by_key(|(buffer, _)| {
6718 buffer.read(cx).file().map(|f| f.path().clone())
6719 });
6720 })?;
6721 if entries.is_empty() {
6722 return Ok(());
6723 }
6724
6725 // If the project transaction's edits are all contained within this editor, then
6726 // avoid opening a new editor to display them.
6727
6728 if let [(buffer, transaction)] = &*entries {
6729 let excerpt = editor.update(cx, |editor, cx| {
6730 editor
6731 .buffer()
6732 .read(cx)
6733 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6734 })?;
6735 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6736 && excerpted_buffer == *buffer
6737 {
6738 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6739 let excerpt_range = excerpt_range.to_offset(buffer);
6740 buffer
6741 .edited_ranges_for_transaction::<usize>(transaction)
6742 .all(|range| {
6743 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6744 })
6745 });
6746
6747 if all_edits_within_excerpt {
6748 return Ok(());
6749 }
6750 }
6751 }
6752
6753 let mut ranges_to_highlight = Vec::new();
6754 let excerpt_buffer = cx.new(|cx| {
6755 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6756 for (buffer_handle, transaction) in &entries {
6757 let edited_ranges = buffer_handle
6758 .read(cx)
6759 .edited_ranges_for_transaction::<Point>(transaction)
6760 .collect::<Vec<_>>();
6761 let (ranges, _) = multibuffer.set_excerpts_for_path(
6762 PathKey::for_buffer(buffer_handle, cx),
6763 buffer_handle.clone(),
6764 edited_ranges,
6765 multibuffer_context_lines(cx),
6766 cx,
6767 );
6768
6769 ranges_to_highlight.extend(ranges);
6770 }
6771 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6772 multibuffer
6773 });
6774
6775 workspace.update_in(cx, |workspace, window, cx| {
6776 let project = workspace.project().clone();
6777 let editor =
6778 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6779 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6780 editor.update(cx, |editor, cx| {
6781 editor.highlight_background::<Self>(
6782 &ranges_to_highlight,
6783 |_, theme| theme.colors().editor_highlighted_line_background,
6784 cx,
6785 );
6786 });
6787 })?;
6788
6789 Ok(())
6790 }
6791
6792 pub fn clear_code_action_providers(&mut self) {
6793 self.code_action_providers.clear();
6794 self.available_code_actions.take();
6795 }
6796
6797 pub fn add_code_action_provider(
6798 &mut self,
6799 provider: Rc<dyn CodeActionProvider>,
6800 window: &mut Window,
6801 cx: &mut Context<Self>,
6802 ) {
6803 if self
6804 .code_action_providers
6805 .iter()
6806 .any(|existing_provider| existing_provider.id() == provider.id())
6807 {
6808 return;
6809 }
6810
6811 self.code_action_providers.push(provider);
6812 self.refresh_code_actions(window, cx);
6813 }
6814
6815 pub fn remove_code_action_provider(
6816 &mut self,
6817 id: Arc<str>,
6818 window: &mut Window,
6819 cx: &mut Context<Self>,
6820 ) {
6821 self.code_action_providers
6822 .retain(|provider| provider.id() != id);
6823 self.refresh_code_actions(window, cx);
6824 }
6825
6826 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6827 !self.code_action_providers.is_empty()
6828 && EditorSettings::get_global(cx).toolbar.code_actions
6829 }
6830
6831 pub fn has_available_code_actions(&self) -> bool {
6832 self.available_code_actions
6833 .as_ref()
6834 .is_some_and(|(_, actions)| !actions.is_empty())
6835 }
6836
6837 fn render_inline_code_actions(
6838 &self,
6839 icon_size: ui::IconSize,
6840 display_row: DisplayRow,
6841 is_active: bool,
6842 cx: &mut Context<Self>,
6843 ) -> AnyElement {
6844 let show_tooltip = !self.context_menu_visible();
6845 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6846 .icon_size(icon_size)
6847 .shape(ui::IconButtonShape::Square)
6848 .icon_color(ui::Color::Hidden)
6849 .toggle_state(is_active)
6850 .when(show_tooltip, |this| {
6851 this.tooltip({
6852 let focus_handle = self.focus_handle.clone();
6853 move |_window, cx| {
6854 Tooltip::for_action_in(
6855 "Toggle Code Actions",
6856 &ToggleCodeActions {
6857 deployed_from: None,
6858 quick_launch: false,
6859 },
6860 &focus_handle,
6861 cx,
6862 )
6863 }
6864 })
6865 })
6866 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6867 window.focus(&editor.focus_handle(cx), cx);
6868 editor.toggle_code_actions(
6869 &crate::actions::ToggleCodeActions {
6870 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6871 display_row,
6872 )),
6873 quick_launch: false,
6874 },
6875 window,
6876 cx,
6877 );
6878 }))
6879 .into_any_element()
6880 }
6881
6882 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6883 &self.context_menu
6884 }
6885
6886 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6887 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6888 cx.background_executor()
6889 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6890 .await;
6891
6892 let (start_buffer, start, _, end, newest_selection) = this
6893 .update(cx, |this, cx| {
6894 let newest_selection = this.selections.newest_anchor().clone();
6895 if newest_selection.head().diff_base_anchor.is_some() {
6896 return None;
6897 }
6898 let display_snapshot = this.display_snapshot(cx);
6899 let newest_selection_adjusted =
6900 this.selections.newest_adjusted(&display_snapshot);
6901 let buffer = this.buffer.read(cx);
6902
6903 let (start_buffer, start) =
6904 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6905 let (end_buffer, end) =
6906 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6907
6908 Some((start_buffer, start, end_buffer, end, newest_selection))
6909 })?
6910 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6911 .context(
6912 "Expected selection to lie in a single buffer when refreshing code actions",
6913 )?;
6914 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6915 let providers = this.code_action_providers.clone();
6916 let tasks = this
6917 .code_action_providers
6918 .iter()
6919 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6920 .collect::<Vec<_>>();
6921 (providers, tasks)
6922 })?;
6923
6924 let mut actions = Vec::new();
6925 for (provider, provider_actions) in
6926 providers.into_iter().zip(future::join_all(tasks).await)
6927 {
6928 if let Some(provider_actions) = provider_actions.log_err() {
6929 actions.extend(provider_actions.into_iter().map(|action| {
6930 AvailableCodeAction {
6931 excerpt_id: newest_selection.start.excerpt_id,
6932 action,
6933 provider: provider.clone(),
6934 }
6935 }));
6936 }
6937 }
6938
6939 this.update(cx, |this, cx| {
6940 this.available_code_actions = if actions.is_empty() {
6941 None
6942 } else {
6943 Some((
6944 Location {
6945 buffer: start_buffer,
6946 range: start..end,
6947 },
6948 actions.into(),
6949 ))
6950 };
6951 cx.notify();
6952 })
6953 }));
6954 }
6955
6956 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6957 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6958 self.show_git_blame_inline = false;
6959
6960 self.show_git_blame_inline_delay_task =
6961 Some(cx.spawn_in(window, async move |this, cx| {
6962 cx.background_executor().timer(delay).await;
6963
6964 this.update(cx, |this, cx| {
6965 this.show_git_blame_inline = true;
6966 cx.notify();
6967 })
6968 .log_err();
6969 }));
6970 }
6971 }
6972
6973 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6974 let snapshot = self.snapshot(window, cx);
6975 let cursor = self
6976 .selections
6977 .newest::<Point>(&snapshot.display_snapshot)
6978 .head();
6979 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6980 else {
6981 return;
6982 };
6983
6984 if self.blame.is_none() {
6985 self.start_git_blame(true, window, cx);
6986 }
6987 let Some(blame) = self.blame.as_ref() else {
6988 return;
6989 };
6990
6991 let row_info = RowInfo {
6992 buffer_id: Some(buffer.remote_id()),
6993 buffer_row: Some(point.row),
6994 ..Default::default()
6995 };
6996 let Some((buffer, blame_entry)) = blame
6997 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6998 .flatten()
6999 else {
7000 return;
7001 };
7002
7003 let anchor = self.selections.newest_anchor().head();
7004 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7005 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7006 self.show_blame_popover(
7007 buffer,
7008 &blame_entry,
7009 position + last_bounds.origin,
7010 true,
7011 cx,
7012 );
7013 };
7014 }
7015
7016 fn show_blame_popover(
7017 &mut self,
7018 buffer: BufferId,
7019 blame_entry: &BlameEntry,
7020 position: gpui::Point<Pixels>,
7021 ignore_timeout: bool,
7022 cx: &mut Context<Self>,
7023 ) {
7024 if let Some(state) = &mut self.inline_blame_popover {
7025 state.hide_task.take();
7026 } else {
7027 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7028 let blame_entry = blame_entry.clone();
7029 let show_task = cx.spawn(async move |editor, cx| {
7030 if !ignore_timeout {
7031 cx.background_executor()
7032 .timer(std::time::Duration::from_millis(blame_popover_delay))
7033 .await;
7034 }
7035 editor
7036 .update(cx, |editor, cx| {
7037 editor.inline_blame_popover_show_task.take();
7038 let Some(blame) = editor.blame.as_ref() else {
7039 return;
7040 };
7041 let blame = blame.read(cx);
7042 let details = blame.details_for_entry(buffer, &blame_entry);
7043 let markdown = cx.new(|cx| {
7044 Markdown::new(
7045 details
7046 .as_ref()
7047 .map(|message| message.message.clone())
7048 .unwrap_or_default(),
7049 None,
7050 None,
7051 cx,
7052 )
7053 });
7054 editor.inline_blame_popover = Some(InlineBlamePopover {
7055 position,
7056 hide_task: None,
7057 popover_bounds: None,
7058 popover_state: InlineBlamePopoverState {
7059 scroll_handle: ScrollHandle::new(),
7060 commit_message: details,
7061 markdown,
7062 },
7063 keyboard_grace: ignore_timeout,
7064 });
7065 cx.notify();
7066 })
7067 .ok();
7068 });
7069 self.inline_blame_popover_show_task = Some(show_task);
7070 }
7071 }
7072
7073 pub fn has_mouse_context_menu(&self) -> bool {
7074 self.mouse_context_menu.is_some()
7075 }
7076
7077 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7078 self.inline_blame_popover_show_task.take();
7079 if let Some(state) = &mut self.inline_blame_popover {
7080 let hide_task = cx.spawn(async move |editor, cx| {
7081 if !ignore_timeout {
7082 cx.background_executor()
7083 .timer(std::time::Duration::from_millis(100))
7084 .await;
7085 }
7086 editor
7087 .update(cx, |editor, cx| {
7088 editor.inline_blame_popover.take();
7089 cx.notify();
7090 })
7091 .ok();
7092 });
7093 state.hide_task = Some(hide_task);
7094 true
7095 } else {
7096 false
7097 }
7098 }
7099
7100 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7101 if self.pending_rename.is_some() {
7102 return None;
7103 }
7104
7105 let provider = self.semantics_provider.clone()?;
7106 let buffer = self.buffer.read(cx);
7107 let newest_selection = self.selections.newest_anchor().clone();
7108 let cursor_position = newest_selection.head();
7109 let (cursor_buffer, cursor_buffer_position) =
7110 buffer.text_anchor_for_position(cursor_position, cx)?;
7111 let (tail_buffer, tail_buffer_position) =
7112 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7113 if cursor_buffer != tail_buffer {
7114 return None;
7115 }
7116
7117 let snapshot = cursor_buffer.read(cx).snapshot();
7118 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7119 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7120 if start_word_range != end_word_range {
7121 self.document_highlights_task.take();
7122 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7123 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7124 return None;
7125 }
7126
7127 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7128 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7129 cx.background_executor()
7130 .timer(Duration::from_millis(debounce))
7131 .await;
7132
7133 let highlights = if let Some(highlights) = cx.update(|cx| {
7134 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7135 }) {
7136 highlights.await.log_err()
7137 } else {
7138 None
7139 };
7140
7141 if let Some(highlights) = highlights {
7142 this.update(cx, |this, cx| {
7143 if this.pending_rename.is_some() {
7144 return;
7145 }
7146
7147 let buffer = this.buffer.read(cx);
7148 if buffer
7149 .text_anchor_for_position(cursor_position, cx)
7150 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7151 {
7152 return;
7153 }
7154
7155 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7156 let mut write_ranges = Vec::new();
7157 let mut read_ranges = Vec::new();
7158 for highlight in highlights {
7159 let buffer_id = cursor_buffer.read(cx).remote_id();
7160 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7161 {
7162 let start = highlight
7163 .range
7164 .start
7165 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7166 let end = highlight
7167 .range
7168 .end
7169 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7170 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7171 continue;
7172 }
7173
7174 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7175 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7176 write_ranges.push(range);
7177 } else {
7178 read_ranges.push(range);
7179 }
7180 }
7181 }
7182
7183 this.highlight_background::<DocumentHighlightRead>(
7184 &read_ranges,
7185 |_, theme| theme.colors().editor_document_highlight_read_background,
7186 cx,
7187 );
7188 this.highlight_background::<DocumentHighlightWrite>(
7189 &write_ranges,
7190 |_, theme| theme.colors().editor_document_highlight_write_background,
7191 cx,
7192 );
7193 cx.notify();
7194 })
7195 .log_err();
7196 }
7197 }));
7198 None
7199 }
7200
7201 fn prepare_highlight_query_from_selection(
7202 &mut self,
7203 window: &Window,
7204 cx: &mut Context<Editor>,
7205 ) -> Option<(String, Range<Anchor>)> {
7206 if matches!(self.mode, EditorMode::SingleLine) {
7207 return None;
7208 }
7209 if !EditorSettings::get_global(cx).selection_highlight {
7210 return None;
7211 }
7212 if self.selections.count() != 1 || self.selections.line_mode() {
7213 return None;
7214 }
7215 let snapshot = self.snapshot(window, cx);
7216 let selection = self.selections.newest::<Point>(&snapshot);
7217 // If the selection spans multiple rows OR it is empty
7218 if selection.start.row != selection.end.row
7219 || selection.start.column == selection.end.column
7220 {
7221 return None;
7222 }
7223 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7224 let query = snapshot
7225 .buffer_snapshot()
7226 .text_for_range(selection_anchor_range.clone())
7227 .collect::<String>();
7228 if query.trim().is_empty() {
7229 return None;
7230 }
7231 Some((query, selection_anchor_range))
7232 }
7233
7234 #[ztracing::instrument(skip_all)]
7235 fn update_selection_occurrence_highlights(
7236 &mut self,
7237 query_text: String,
7238 query_range: Range<Anchor>,
7239 multi_buffer_range_to_query: Range<Point>,
7240 use_debounce: bool,
7241 window: &mut Window,
7242 cx: &mut Context<Editor>,
7243 ) -> Task<()> {
7244 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7245 cx.spawn_in(window, async move |editor, cx| {
7246 if use_debounce {
7247 cx.background_executor()
7248 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7249 .await;
7250 }
7251 let match_task = cx.background_spawn(async move {
7252 let buffer_ranges = multi_buffer_snapshot
7253 .range_to_buffer_ranges(multi_buffer_range_to_query)
7254 .into_iter()
7255 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7256 let mut match_ranges = Vec::new();
7257 let Ok(regex) = project::search::SearchQuery::text(
7258 query_text.clone(),
7259 false,
7260 false,
7261 false,
7262 Default::default(),
7263 Default::default(),
7264 false,
7265 None,
7266 ) else {
7267 return Vec::default();
7268 };
7269 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7270 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7271 match_ranges.extend(
7272 regex
7273 .search(
7274 buffer_snapshot,
7275 Some(search_range.start.0..search_range.end.0),
7276 )
7277 .await
7278 .into_iter()
7279 .filter_map(|match_range| {
7280 let match_start = buffer_snapshot
7281 .anchor_after(search_range.start + match_range.start);
7282 let match_end = buffer_snapshot
7283 .anchor_before(search_range.start + match_range.end);
7284 let match_anchor_range =
7285 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7286 (match_anchor_range != query_range).then_some(match_anchor_range)
7287 }),
7288 );
7289 }
7290 match_ranges
7291 });
7292 let match_ranges = match_task.await;
7293 editor
7294 .update_in(cx, |editor, _, cx| {
7295 if use_debounce {
7296 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7297 editor.debounced_selection_highlight_complete = true;
7298 } else if editor.debounced_selection_highlight_complete {
7299 return;
7300 }
7301 if !match_ranges.is_empty() {
7302 editor.highlight_background::<SelectedTextHighlight>(
7303 &match_ranges,
7304 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7305 cx,
7306 )
7307 }
7308 })
7309 .log_err();
7310 })
7311 }
7312
7313 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7314 struct NewlineFold;
7315 let type_id = std::any::TypeId::of::<NewlineFold>();
7316 if !self.mode.is_single_line() {
7317 return;
7318 }
7319 let snapshot = self.snapshot(window, cx);
7320 if snapshot.buffer_snapshot().max_point().row == 0 {
7321 return;
7322 }
7323 let task = cx.background_spawn(async move {
7324 let new_newlines = snapshot
7325 .buffer_chars_at(MultiBufferOffset(0))
7326 .filter_map(|(c, i)| {
7327 if c == '\n' {
7328 Some(
7329 snapshot.buffer_snapshot().anchor_after(i)
7330 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7331 )
7332 } else {
7333 None
7334 }
7335 })
7336 .collect::<Vec<_>>();
7337 let existing_newlines = snapshot
7338 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7339 .filter_map(|fold| {
7340 if fold.placeholder.type_tag == Some(type_id) {
7341 Some(fold.range.start..fold.range.end)
7342 } else {
7343 None
7344 }
7345 })
7346 .collect::<Vec<_>>();
7347
7348 (new_newlines, existing_newlines)
7349 });
7350 self.folding_newlines = cx.spawn(async move |this, cx| {
7351 let (new_newlines, existing_newlines) = task.await;
7352 if new_newlines == existing_newlines {
7353 return;
7354 }
7355 let placeholder = FoldPlaceholder {
7356 render: Arc::new(move |_, _, cx| {
7357 div()
7358 .bg(cx.theme().status().hint_background)
7359 .border_b_1()
7360 .size_full()
7361 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7362 .border_color(cx.theme().status().hint)
7363 .child("\\n")
7364 .into_any()
7365 }),
7366 constrain_width: false,
7367 merge_adjacent: false,
7368 type_tag: Some(type_id),
7369 };
7370 let creases = new_newlines
7371 .into_iter()
7372 .map(|range| Crease::simple(range, placeholder.clone()))
7373 .collect();
7374 this.update(cx, |this, cx| {
7375 this.display_map.update(cx, |display_map, cx| {
7376 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7377 display_map.fold(creases, cx);
7378 });
7379 })
7380 .ok();
7381 });
7382 }
7383
7384 #[ztracing::instrument(skip_all)]
7385 fn refresh_selected_text_highlights(
7386 &mut self,
7387 on_buffer_edit: bool,
7388 window: &mut Window,
7389 cx: &mut Context<Editor>,
7390 ) {
7391 let Some((query_text, query_range)) =
7392 self.prepare_highlight_query_from_selection(window, cx)
7393 else {
7394 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7395 self.quick_selection_highlight_task.take();
7396 self.debounced_selection_highlight_task.take();
7397 self.debounced_selection_highlight_complete = false;
7398 return;
7399 };
7400 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7401 let query_changed = self
7402 .quick_selection_highlight_task
7403 .as_ref()
7404 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7405 if query_changed {
7406 self.debounced_selection_highlight_complete = false;
7407 }
7408 if on_buffer_edit || query_changed {
7409 let multi_buffer_visible_start = self
7410 .scroll_manager
7411 .anchor()
7412 .anchor
7413 .to_point(&multi_buffer_snapshot);
7414 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7415 multi_buffer_visible_start
7416 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7417 Bias::Left,
7418 );
7419 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7420 self.quick_selection_highlight_task = Some((
7421 query_range.clone(),
7422 self.update_selection_occurrence_highlights(
7423 query_text.clone(),
7424 query_range.clone(),
7425 multi_buffer_visible_range,
7426 false,
7427 window,
7428 cx,
7429 ),
7430 ));
7431 }
7432 if on_buffer_edit
7433 || self
7434 .debounced_selection_highlight_task
7435 .as_ref()
7436 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7437 {
7438 let multi_buffer_start = multi_buffer_snapshot
7439 .anchor_before(MultiBufferOffset(0))
7440 .to_point(&multi_buffer_snapshot);
7441 let multi_buffer_end = multi_buffer_snapshot
7442 .anchor_after(multi_buffer_snapshot.len())
7443 .to_point(&multi_buffer_snapshot);
7444 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7445 self.debounced_selection_highlight_task = Some((
7446 query_range.clone(),
7447 self.update_selection_occurrence_highlights(
7448 query_text,
7449 query_range,
7450 multi_buffer_full_range,
7451 true,
7452 window,
7453 cx,
7454 ),
7455 ));
7456 }
7457 }
7458
7459 pub fn refresh_edit_prediction(
7460 &mut self,
7461 debounce: bool,
7462 user_requested: bool,
7463 window: &mut Window,
7464 cx: &mut Context<Self>,
7465 ) -> Option<()> {
7466 if DisableAiSettings::get_global(cx).disable_ai {
7467 return None;
7468 }
7469
7470 let provider = self.edit_prediction_provider()?;
7471 let cursor = self.selections.newest_anchor().head();
7472 let (buffer, cursor_buffer_position) =
7473 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7474
7475 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7476 self.discard_edit_prediction(false, cx);
7477 return None;
7478 }
7479
7480 self.update_visible_edit_prediction(window, cx);
7481
7482 if !user_requested
7483 && (!self.should_show_edit_predictions()
7484 || !self.is_focused(window)
7485 || buffer.read(cx).is_empty())
7486 {
7487 self.discard_edit_prediction(false, cx);
7488 return None;
7489 }
7490
7491 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7492 Some(())
7493 }
7494
7495 fn show_edit_predictions_in_menu(&self) -> bool {
7496 match self.edit_prediction_settings {
7497 EditPredictionSettings::Disabled => false,
7498 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7499 }
7500 }
7501
7502 pub fn edit_predictions_enabled(&self) -> bool {
7503 match self.edit_prediction_settings {
7504 EditPredictionSettings::Disabled => false,
7505 EditPredictionSettings::Enabled { .. } => true,
7506 }
7507 }
7508
7509 fn edit_prediction_requires_modifier(&self) -> bool {
7510 match self.edit_prediction_settings {
7511 EditPredictionSettings::Disabled => false,
7512 EditPredictionSettings::Enabled {
7513 preview_requires_modifier,
7514 ..
7515 } => preview_requires_modifier,
7516 }
7517 }
7518
7519 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7520 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7521 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7522 self.discard_edit_prediction(false, cx);
7523 } else {
7524 let selection = self.selections.newest_anchor();
7525 let cursor = selection.head();
7526
7527 if let Some((buffer, cursor_buffer_position)) =
7528 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7529 {
7530 self.edit_prediction_settings =
7531 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7532 }
7533 }
7534 }
7535
7536 fn edit_prediction_settings_at_position(
7537 &self,
7538 buffer: &Entity<Buffer>,
7539 buffer_position: language::Anchor,
7540 cx: &App,
7541 ) -> EditPredictionSettings {
7542 if !self.mode.is_full()
7543 || !self.show_edit_predictions_override.unwrap_or(true)
7544 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7545 {
7546 return EditPredictionSettings::Disabled;
7547 }
7548
7549 let buffer = buffer.read(cx);
7550
7551 let file = buffer.file();
7552
7553 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7554 return EditPredictionSettings::Disabled;
7555 };
7556
7557 let by_provider = matches!(
7558 self.menu_edit_predictions_policy,
7559 MenuEditPredictionsPolicy::ByProvider
7560 );
7561
7562 let show_in_menu = by_provider
7563 && self
7564 .edit_prediction_provider
7565 .as_ref()
7566 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7567
7568 let preview_requires_modifier =
7569 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7570
7571 EditPredictionSettings::Enabled {
7572 show_in_menu,
7573 preview_requires_modifier,
7574 }
7575 }
7576
7577 fn should_show_edit_predictions(&self) -> bool {
7578 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7579 }
7580
7581 pub fn edit_prediction_preview_is_active(&self) -> bool {
7582 matches!(
7583 self.edit_prediction_preview,
7584 EditPredictionPreview::Active { .. }
7585 )
7586 }
7587
7588 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7589 let cursor = self.selections.newest_anchor().head();
7590 if let Some((buffer, cursor_position)) =
7591 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7592 {
7593 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7594 } else {
7595 false
7596 }
7597 }
7598
7599 pub fn supports_minimap(&self, cx: &App) -> bool {
7600 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7601 }
7602
7603 fn edit_predictions_enabled_in_buffer(
7604 &self,
7605 buffer: &Entity<Buffer>,
7606 buffer_position: language::Anchor,
7607 cx: &App,
7608 ) -> bool {
7609 maybe!({
7610 if self.read_only(cx) {
7611 return Some(false);
7612 }
7613 let provider = self.edit_prediction_provider()?;
7614 if !provider.is_enabled(buffer, buffer_position, cx) {
7615 return Some(false);
7616 }
7617 let buffer = buffer.read(cx);
7618 let Some(file) = buffer.file() else {
7619 return Some(true);
7620 };
7621 let settings = all_language_settings(Some(file), cx);
7622 Some(settings.edit_predictions_enabled_for_file(file, cx))
7623 })
7624 .unwrap_or(false)
7625 }
7626
7627 pub fn show_edit_prediction(
7628 &mut self,
7629 _: &ShowEditPrediction,
7630 window: &mut Window,
7631 cx: &mut Context<Self>,
7632 ) {
7633 if !self.has_active_edit_prediction() {
7634 self.refresh_edit_prediction(false, true, window, cx);
7635 return;
7636 }
7637
7638 self.update_visible_edit_prediction(window, cx);
7639 }
7640
7641 pub fn display_cursor_names(
7642 &mut self,
7643 _: &DisplayCursorNames,
7644 window: &mut Window,
7645 cx: &mut Context<Self>,
7646 ) {
7647 self.show_cursor_names(window, cx);
7648 }
7649
7650 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7651 self.show_cursor_names = true;
7652 cx.notify();
7653 cx.spawn_in(window, async move |this, cx| {
7654 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7655 this.update(cx, |this, cx| {
7656 this.show_cursor_names = false;
7657 cx.notify()
7658 })
7659 .ok()
7660 })
7661 .detach();
7662 }
7663
7664 pub fn accept_partial_edit_prediction(
7665 &mut self,
7666 granularity: EditPredictionGranularity,
7667 window: &mut Window,
7668 cx: &mut Context<Self>,
7669 ) {
7670 if self.show_edit_predictions_in_menu() {
7671 self.hide_context_menu(window, cx);
7672 }
7673
7674 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7675 return;
7676 };
7677
7678 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7679 return;
7680 }
7681
7682 match &active_edit_prediction.completion {
7683 EditPrediction::MoveWithin { target, .. } => {
7684 let target = *target;
7685
7686 if matches!(granularity, EditPredictionGranularity::Full) {
7687 if let Some(position_map) = &self.last_position_map {
7688 let target_row = target.to_display_point(&position_map.snapshot).row();
7689 let is_visible = position_map.visible_row_range.contains(&target_row);
7690
7691 if is_visible || !self.edit_prediction_requires_modifier() {
7692 self.unfold_ranges(&[target..target], true, false, cx);
7693 self.change_selections(
7694 SelectionEffects::scroll(Autoscroll::newest()),
7695 window,
7696 cx,
7697 |selections| {
7698 selections.select_anchor_ranges([target..target]);
7699 },
7700 );
7701 self.clear_row_highlights::<EditPredictionPreview>();
7702 self.edit_prediction_preview
7703 .set_previous_scroll_position(None);
7704 } else {
7705 // Highlight and request scroll
7706 self.edit_prediction_preview
7707 .set_previous_scroll_position(Some(
7708 position_map.snapshot.scroll_anchor,
7709 ));
7710 self.highlight_rows::<EditPredictionPreview>(
7711 target..target,
7712 cx.theme().colors().editor_highlighted_line_background,
7713 RowHighlightOptions {
7714 autoscroll: true,
7715 ..Default::default()
7716 },
7717 cx,
7718 );
7719 self.request_autoscroll(Autoscroll::fit(), cx);
7720 }
7721 }
7722 } else {
7723 self.change_selections(
7724 SelectionEffects::scroll(Autoscroll::newest()),
7725 window,
7726 cx,
7727 |selections| {
7728 selections.select_anchor_ranges([target..target]);
7729 },
7730 );
7731 }
7732 }
7733 EditPrediction::MoveOutside { snapshot, target } => {
7734 if let Some(workspace) = self.workspace() {
7735 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7736 .detach_and_log_err(cx);
7737 }
7738 }
7739 EditPrediction::Edit { edits, .. } => {
7740 self.report_edit_prediction_event(
7741 active_edit_prediction.completion_id.clone(),
7742 true,
7743 cx,
7744 );
7745
7746 match granularity {
7747 EditPredictionGranularity::Full => {
7748 if let Some(provider) = self.edit_prediction_provider() {
7749 provider.accept(cx);
7750 }
7751
7752 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7753 let snapshot = self.buffer.read(cx).snapshot(cx);
7754 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7755
7756 self.buffer.update(cx, |buffer, cx| {
7757 buffer.edit(edits.iter().cloned(), None, cx)
7758 });
7759
7760 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7761 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7762 });
7763
7764 let selections = self.selections.disjoint_anchors_arc();
7765 if let Some(transaction_id_now) =
7766 self.buffer.read(cx).last_transaction_id(cx)
7767 {
7768 if transaction_id_prev != Some(transaction_id_now) {
7769 self.selection_history
7770 .insert_transaction(transaction_id_now, selections);
7771 }
7772 }
7773
7774 self.update_visible_edit_prediction(window, cx);
7775 if self.active_edit_prediction.is_none() {
7776 self.refresh_edit_prediction(true, true, window, cx);
7777 }
7778 cx.notify();
7779 }
7780 _ => {
7781 let snapshot = self.buffer.read(cx).snapshot(cx);
7782 let cursor_offset = self
7783 .selections
7784 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7785 .head();
7786
7787 let insertion = edits.iter().find_map(|(range, text)| {
7788 let range = range.to_offset(&snapshot);
7789 if range.is_empty() && range.start == cursor_offset {
7790 Some(text)
7791 } else {
7792 None
7793 }
7794 });
7795
7796 if let Some(text) = insertion {
7797 let text_to_insert = match granularity {
7798 EditPredictionGranularity::Word => {
7799 let mut partial = text
7800 .chars()
7801 .by_ref()
7802 .take_while(|c| c.is_alphabetic())
7803 .collect::<String>();
7804 if partial.is_empty() {
7805 partial = text
7806 .chars()
7807 .by_ref()
7808 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7809 .collect::<String>();
7810 }
7811 partial
7812 }
7813 EditPredictionGranularity::Line => {
7814 if let Some(line) = text.split_inclusive('\n').next() {
7815 line.to_string()
7816 } else {
7817 text.to_string()
7818 }
7819 }
7820 EditPredictionGranularity::Full => unreachable!(),
7821 };
7822
7823 cx.emit(EditorEvent::InputHandled {
7824 utf16_range_to_replace: None,
7825 text: text_to_insert.clone().into(),
7826 });
7827
7828 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7829 self.refresh_edit_prediction(true, true, window, cx);
7830 cx.notify();
7831 } else {
7832 self.accept_partial_edit_prediction(
7833 EditPredictionGranularity::Full,
7834 window,
7835 cx,
7836 );
7837 }
7838 }
7839 }
7840 }
7841 }
7842
7843 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7844 }
7845
7846 pub fn accept_next_word_edit_prediction(
7847 &mut self,
7848 _: &AcceptNextWordEditPrediction,
7849 window: &mut Window,
7850 cx: &mut Context<Self>,
7851 ) {
7852 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7853 }
7854
7855 pub fn accept_next_line_edit_prediction(
7856 &mut self,
7857 _: &AcceptNextLineEditPrediction,
7858 window: &mut Window,
7859 cx: &mut Context<Self>,
7860 ) {
7861 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7862 }
7863
7864 pub fn accept_edit_prediction(
7865 &mut self,
7866 _: &AcceptEditPrediction,
7867 window: &mut Window,
7868 cx: &mut Context<Self>,
7869 ) {
7870 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7871 }
7872
7873 fn discard_edit_prediction(
7874 &mut self,
7875 should_report_edit_prediction_event: bool,
7876 cx: &mut Context<Self>,
7877 ) -> bool {
7878 if should_report_edit_prediction_event {
7879 let completion_id = self
7880 .active_edit_prediction
7881 .as_ref()
7882 .and_then(|active_completion| active_completion.completion_id.clone());
7883
7884 self.report_edit_prediction_event(completion_id, false, cx);
7885 }
7886
7887 if let Some(provider) = self.edit_prediction_provider() {
7888 provider.discard(cx);
7889 }
7890
7891 self.take_active_edit_prediction(cx)
7892 }
7893
7894 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7895 let Some(provider) = self.edit_prediction_provider() else {
7896 return;
7897 };
7898
7899 let Some((_, buffer, _)) = self
7900 .buffer
7901 .read(cx)
7902 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7903 else {
7904 return;
7905 };
7906
7907 let extension = buffer
7908 .read(cx)
7909 .file()
7910 .and_then(|file| Some(file.path().extension()?.to_string()));
7911
7912 let event_type = match accepted {
7913 true => "Edit Prediction Accepted",
7914 false => "Edit Prediction Discarded",
7915 };
7916 telemetry::event!(
7917 event_type,
7918 provider = provider.name(),
7919 prediction_id = id,
7920 suggestion_accepted = accepted,
7921 file_extension = extension,
7922 );
7923 }
7924
7925 fn open_editor_at_anchor(
7926 snapshot: &language::BufferSnapshot,
7927 target: language::Anchor,
7928 workspace: &Entity<Workspace>,
7929 window: &mut Window,
7930 cx: &mut App,
7931 ) -> Task<Result<()>> {
7932 workspace.update(cx, |workspace, cx| {
7933 let path = snapshot.file().map(|file| file.full_path(cx));
7934 let Some(path) =
7935 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7936 else {
7937 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7938 };
7939 let target = text::ToPoint::to_point(&target, snapshot);
7940 let item = workspace.open_path(path, None, true, window, cx);
7941 window.spawn(cx, async move |cx| {
7942 let Some(editor) = item.await?.downcast::<Editor>() else {
7943 return Ok(());
7944 };
7945 editor
7946 .update_in(cx, |editor, window, cx| {
7947 editor.go_to_singleton_buffer_point(target, window, cx);
7948 })
7949 .ok();
7950 anyhow::Ok(())
7951 })
7952 })
7953 }
7954
7955 pub fn has_active_edit_prediction(&self) -> bool {
7956 self.active_edit_prediction.is_some()
7957 }
7958
7959 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7960 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7961 return false;
7962 };
7963
7964 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7965 self.clear_highlights::<EditPredictionHighlight>(cx);
7966 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7967 true
7968 }
7969
7970 /// Returns true when we're displaying the edit prediction popover below the cursor
7971 /// like we are not previewing and the LSP autocomplete menu is visible
7972 /// or we are in `when_holding_modifier` mode.
7973 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7974 if self.edit_prediction_preview_is_active()
7975 || !self.show_edit_predictions_in_menu()
7976 || !self.edit_predictions_enabled()
7977 {
7978 return false;
7979 }
7980
7981 if self.has_visible_completions_menu() {
7982 return true;
7983 }
7984
7985 has_completion && self.edit_prediction_requires_modifier()
7986 }
7987
7988 fn handle_modifiers_changed(
7989 &mut self,
7990 modifiers: Modifiers,
7991 position_map: &PositionMap,
7992 window: &mut Window,
7993 cx: &mut Context<Self>,
7994 ) {
7995 // Ensure that the edit prediction preview is updated, even when not
7996 // enabled, if there's an active edit prediction preview.
7997 if self.show_edit_predictions_in_menu()
7998 || matches!(
7999 self.edit_prediction_preview,
8000 EditPredictionPreview::Active { .. }
8001 )
8002 {
8003 self.update_edit_prediction_preview(&modifiers, window, cx);
8004 }
8005
8006 self.update_selection_mode(&modifiers, position_map, window, cx);
8007
8008 let mouse_position = window.mouse_position();
8009 if !position_map.text_hitbox.is_hovered(window) {
8010 return;
8011 }
8012
8013 self.update_hovered_link(
8014 position_map.point_for_position(mouse_position),
8015 &position_map.snapshot,
8016 modifiers,
8017 window,
8018 cx,
8019 )
8020 }
8021
8022 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8023 match EditorSettings::get_global(cx).multi_cursor_modifier {
8024 MultiCursorModifier::Alt => modifiers.secondary(),
8025 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8026 }
8027 }
8028
8029 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8030 match EditorSettings::get_global(cx).multi_cursor_modifier {
8031 MultiCursorModifier::Alt => modifiers.alt,
8032 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8033 }
8034 }
8035
8036 fn columnar_selection_mode(
8037 modifiers: &Modifiers,
8038 cx: &mut Context<Self>,
8039 ) -> Option<ColumnarMode> {
8040 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8041 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8042 Some(ColumnarMode::FromMouse)
8043 } else if Self::is_alt_pressed(modifiers, cx) {
8044 Some(ColumnarMode::FromSelection)
8045 } else {
8046 None
8047 }
8048 } else {
8049 None
8050 }
8051 }
8052
8053 fn update_selection_mode(
8054 &mut self,
8055 modifiers: &Modifiers,
8056 position_map: &PositionMap,
8057 window: &mut Window,
8058 cx: &mut Context<Self>,
8059 ) {
8060 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8061 return;
8062 };
8063 if self.selections.pending_anchor().is_none() {
8064 return;
8065 }
8066
8067 let mouse_position = window.mouse_position();
8068 let point_for_position = position_map.point_for_position(mouse_position);
8069 let position = point_for_position.previous_valid;
8070
8071 self.select(
8072 SelectPhase::BeginColumnar {
8073 position,
8074 reset: false,
8075 mode,
8076 goal_column: point_for_position.exact_unclipped.column(),
8077 },
8078 window,
8079 cx,
8080 );
8081 }
8082
8083 fn update_edit_prediction_preview(
8084 &mut self,
8085 modifiers: &Modifiers,
8086 window: &mut Window,
8087 cx: &mut Context<Self>,
8088 ) {
8089 let mut modifiers_held = false;
8090
8091 // Check bindings for all granularities.
8092 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8093 let granularities = [
8094 EditPredictionGranularity::Full,
8095 EditPredictionGranularity::Line,
8096 EditPredictionGranularity::Word,
8097 ];
8098
8099 for granularity in granularities {
8100 if let Some(keystroke) = self
8101 .accept_edit_prediction_keybind(granularity, window, cx)
8102 .keystroke()
8103 {
8104 modifiers_held = modifiers_held
8105 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8106 }
8107 }
8108
8109 if modifiers_held {
8110 if matches!(
8111 self.edit_prediction_preview,
8112 EditPredictionPreview::Inactive { .. }
8113 ) {
8114 self.edit_prediction_preview = EditPredictionPreview::Active {
8115 previous_scroll_position: None,
8116 since: Instant::now(),
8117 };
8118
8119 self.update_visible_edit_prediction(window, cx);
8120 cx.notify();
8121 }
8122 } else if let EditPredictionPreview::Active {
8123 previous_scroll_position,
8124 since,
8125 } = self.edit_prediction_preview
8126 {
8127 if let (Some(previous_scroll_position), Some(position_map)) =
8128 (previous_scroll_position, self.last_position_map.as_ref())
8129 {
8130 self.set_scroll_position(
8131 previous_scroll_position
8132 .scroll_position(&position_map.snapshot.display_snapshot),
8133 window,
8134 cx,
8135 );
8136 }
8137
8138 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8139 released_too_fast: since.elapsed() < Duration::from_millis(200),
8140 };
8141 self.clear_row_highlights::<EditPredictionPreview>();
8142 self.update_visible_edit_prediction(window, cx);
8143 cx.notify();
8144 }
8145 }
8146
8147 fn update_visible_edit_prediction(
8148 &mut self,
8149 _window: &mut Window,
8150 cx: &mut Context<Self>,
8151 ) -> Option<()> {
8152 if DisableAiSettings::get_global(cx).disable_ai {
8153 return None;
8154 }
8155
8156 if self.ime_transaction.is_some() {
8157 self.discard_edit_prediction(false, cx);
8158 return None;
8159 }
8160
8161 let selection = self.selections.newest_anchor();
8162 let cursor = selection.head();
8163 let multibuffer = self.buffer.read(cx).snapshot(cx);
8164 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8165 let excerpt_id = cursor.excerpt_id;
8166
8167 let show_in_menu = self.show_edit_predictions_in_menu();
8168 let completions_menu_has_precedence = !show_in_menu
8169 && (self.context_menu.borrow().is_some()
8170 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8171
8172 if completions_menu_has_precedence
8173 || !offset_selection.is_empty()
8174 || self
8175 .active_edit_prediction
8176 .as_ref()
8177 .is_some_and(|completion| {
8178 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8179 return false;
8180 };
8181 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8182 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8183 !invalidation_range.contains(&offset_selection.head())
8184 })
8185 {
8186 self.discard_edit_prediction(false, cx);
8187 return None;
8188 }
8189
8190 self.take_active_edit_prediction(cx);
8191 let Some(provider) = self.edit_prediction_provider() else {
8192 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8193 return None;
8194 };
8195
8196 let (buffer, cursor_buffer_position) =
8197 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8198
8199 self.edit_prediction_settings =
8200 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8201
8202 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8203
8204 if self.edit_prediction_indent_conflict {
8205 let cursor_point = cursor.to_point(&multibuffer);
8206 let mut suggested_indent = None;
8207 multibuffer.suggested_indents_callback(
8208 cursor_point.row..cursor_point.row + 1,
8209 |_, indent| {
8210 suggested_indent = Some(indent);
8211 ControlFlow::Break(())
8212 },
8213 cx,
8214 );
8215
8216 if let Some(indent) = suggested_indent
8217 && indent.len == cursor_point.column
8218 {
8219 self.edit_prediction_indent_conflict = false;
8220 }
8221 }
8222
8223 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8224
8225 let (completion_id, edits, edit_preview) = match edit_prediction {
8226 edit_prediction_types::EditPrediction::Local {
8227 id,
8228 edits,
8229 edit_preview,
8230 } => (id, edits, edit_preview),
8231 edit_prediction_types::EditPrediction::Jump {
8232 id,
8233 snapshot,
8234 target,
8235 } => {
8236 if let Some(provider) = &self.edit_prediction_provider {
8237 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8238 }
8239 self.stale_edit_prediction_in_menu = None;
8240 self.active_edit_prediction = Some(EditPredictionState {
8241 inlay_ids: vec![],
8242 completion: EditPrediction::MoveOutside { snapshot, target },
8243 completion_id: id,
8244 invalidation_range: None,
8245 });
8246 cx.notify();
8247 return Some(());
8248 }
8249 };
8250
8251 let edits = edits
8252 .into_iter()
8253 .flat_map(|(range, new_text)| {
8254 Some((
8255 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8256 new_text,
8257 ))
8258 })
8259 .collect::<Vec<_>>();
8260 if edits.is_empty() {
8261 return None;
8262 }
8263
8264 let first_edit_start = edits.first().unwrap().0.start;
8265 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8266 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8267
8268 let last_edit_end = edits.last().unwrap().0.end;
8269 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8270 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8271
8272 let cursor_row = cursor.to_point(&multibuffer).row;
8273
8274 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8275
8276 let mut inlay_ids = Vec::new();
8277 let invalidation_row_range;
8278 let move_invalidation_row_range = if cursor_row < edit_start_row {
8279 Some(cursor_row..edit_end_row)
8280 } else if cursor_row > edit_end_row {
8281 Some(edit_start_row..cursor_row)
8282 } else {
8283 None
8284 };
8285 let supports_jump = self
8286 .edit_prediction_provider
8287 .as_ref()
8288 .map(|provider| provider.provider.supports_jump_to_edit())
8289 .unwrap_or(true);
8290
8291 let is_move = supports_jump
8292 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8293 let completion = if is_move {
8294 if let Some(provider) = &self.edit_prediction_provider {
8295 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8296 }
8297 invalidation_row_range =
8298 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8299 let target = first_edit_start;
8300 EditPrediction::MoveWithin { target, snapshot }
8301 } else {
8302 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8303 && !self.edit_predictions_hidden_for_vim_mode;
8304
8305 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8306 if provider.show_tab_accept_marker() {
8307 EditDisplayMode::TabAccept
8308 } else {
8309 EditDisplayMode::Inline
8310 }
8311 } else {
8312 EditDisplayMode::DiffPopover
8313 };
8314
8315 if show_completions_in_buffer {
8316 if let Some(provider) = &self.edit_prediction_provider {
8317 let suggestion_display_type = match display_mode {
8318 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8319 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8320 SuggestionDisplayType::GhostText
8321 }
8322 };
8323 provider.provider.did_show(suggestion_display_type, cx);
8324 }
8325 if edits
8326 .iter()
8327 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8328 {
8329 let mut inlays = Vec::new();
8330 for (range, new_text) in &edits {
8331 let inlay = Inlay::edit_prediction(
8332 post_inc(&mut self.next_inlay_id),
8333 range.start,
8334 new_text.as_ref(),
8335 );
8336 inlay_ids.push(inlay.id);
8337 inlays.push(inlay);
8338 }
8339
8340 self.splice_inlays(&[], inlays, cx);
8341 } else {
8342 let background_color = cx.theme().status().deleted_background;
8343 self.highlight_text::<EditPredictionHighlight>(
8344 edits.iter().map(|(range, _)| range.clone()).collect(),
8345 HighlightStyle {
8346 background_color: Some(background_color),
8347 ..Default::default()
8348 },
8349 cx,
8350 );
8351 }
8352 }
8353
8354 invalidation_row_range = edit_start_row..edit_end_row;
8355
8356 EditPrediction::Edit {
8357 edits,
8358 edit_preview,
8359 display_mode,
8360 snapshot,
8361 }
8362 };
8363
8364 let invalidation_range = multibuffer
8365 .anchor_before(Point::new(invalidation_row_range.start, 0))
8366 ..multibuffer.anchor_after(Point::new(
8367 invalidation_row_range.end,
8368 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8369 ));
8370
8371 self.stale_edit_prediction_in_menu = None;
8372 self.active_edit_prediction = Some(EditPredictionState {
8373 inlay_ids,
8374 completion,
8375 completion_id,
8376 invalidation_range: Some(invalidation_range),
8377 });
8378
8379 cx.notify();
8380
8381 Some(())
8382 }
8383
8384 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8385 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8386 }
8387
8388 fn clear_tasks(&mut self) {
8389 self.tasks.clear()
8390 }
8391
8392 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8393 if self.tasks.insert(key, value).is_some() {
8394 // This case should hopefully be rare, but just in case...
8395 log::error!(
8396 "multiple different run targets found on a single line, only the last target will be rendered"
8397 )
8398 }
8399 }
8400
8401 /// Get all display points of breakpoints that will be rendered within editor
8402 ///
8403 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8404 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8405 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8406 fn active_breakpoints(
8407 &self,
8408 range: Range<DisplayRow>,
8409 window: &mut Window,
8410 cx: &mut Context<Self>,
8411 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8412 let mut breakpoint_display_points = HashMap::default();
8413
8414 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8415 return breakpoint_display_points;
8416 };
8417
8418 let snapshot = self.snapshot(window, cx);
8419
8420 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8421 let Some(project) = self.project() else {
8422 return breakpoint_display_points;
8423 };
8424
8425 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8426 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8427
8428 for (buffer_snapshot, range, excerpt_id) in
8429 multi_buffer_snapshot.range_to_buffer_ranges(range)
8430 {
8431 let Some(buffer) = project
8432 .read(cx)
8433 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8434 else {
8435 continue;
8436 };
8437 let breakpoints = breakpoint_store.read(cx).breakpoints(
8438 &buffer,
8439 Some(
8440 buffer_snapshot.anchor_before(range.start)
8441 ..buffer_snapshot.anchor_after(range.end),
8442 ),
8443 buffer_snapshot,
8444 cx,
8445 );
8446 for (breakpoint, state) in breakpoints {
8447 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8448 let position = multi_buffer_anchor
8449 .to_point(&multi_buffer_snapshot)
8450 .to_display_point(&snapshot);
8451
8452 breakpoint_display_points.insert(
8453 position.row(),
8454 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8455 );
8456 }
8457 }
8458
8459 breakpoint_display_points
8460 }
8461
8462 fn breakpoint_context_menu(
8463 &self,
8464 anchor: Anchor,
8465 window: &mut Window,
8466 cx: &mut Context<Self>,
8467 ) -> Entity<ui::ContextMenu> {
8468 let weak_editor = cx.weak_entity();
8469 let focus_handle = self.focus_handle(cx);
8470
8471 let row = self
8472 .buffer
8473 .read(cx)
8474 .snapshot(cx)
8475 .summary_for_anchor::<Point>(&anchor)
8476 .row;
8477
8478 let breakpoint = self
8479 .breakpoint_at_row(row, window, cx)
8480 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8481
8482 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8483 "Edit Log Breakpoint"
8484 } else {
8485 "Set Log Breakpoint"
8486 };
8487
8488 let condition_breakpoint_msg = if breakpoint
8489 .as_ref()
8490 .is_some_and(|bp| bp.1.condition.is_some())
8491 {
8492 "Edit Condition Breakpoint"
8493 } else {
8494 "Set Condition Breakpoint"
8495 };
8496
8497 let hit_condition_breakpoint_msg = if breakpoint
8498 .as_ref()
8499 .is_some_and(|bp| bp.1.hit_condition.is_some())
8500 {
8501 "Edit Hit Condition Breakpoint"
8502 } else {
8503 "Set Hit Condition Breakpoint"
8504 };
8505
8506 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8507 "Unset Breakpoint"
8508 } else {
8509 "Set Breakpoint"
8510 };
8511
8512 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8513
8514 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8515 BreakpointState::Enabled => Some("Disable"),
8516 BreakpointState::Disabled => Some("Enable"),
8517 });
8518
8519 let (anchor, breakpoint) =
8520 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8521
8522 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8523 menu.on_blur_subscription(Subscription::new(|| {}))
8524 .context(focus_handle)
8525 .when(run_to_cursor, |this| {
8526 let weak_editor = weak_editor.clone();
8527 this.entry("Run to cursor", None, move |window, cx| {
8528 weak_editor
8529 .update(cx, |editor, cx| {
8530 editor.change_selections(
8531 SelectionEffects::no_scroll(),
8532 window,
8533 cx,
8534 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8535 );
8536 })
8537 .ok();
8538
8539 window.dispatch_action(Box::new(RunToCursor), cx);
8540 })
8541 .separator()
8542 })
8543 .when_some(toggle_state_msg, |this, msg| {
8544 this.entry(msg, None, {
8545 let weak_editor = weak_editor.clone();
8546 let breakpoint = breakpoint.clone();
8547 move |_window, cx| {
8548 weak_editor
8549 .update(cx, |this, cx| {
8550 this.edit_breakpoint_at_anchor(
8551 anchor,
8552 breakpoint.as_ref().clone(),
8553 BreakpointEditAction::InvertState,
8554 cx,
8555 );
8556 })
8557 .log_err();
8558 }
8559 })
8560 })
8561 .entry(set_breakpoint_msg, None, {
8562 let weak_editor = weak_editor.clone();
8563 let breakpoint = breakpoint.clone();
8564 move |_window, cx| {
8565 weak_editor
8566 .update(cx, |this, cx| {
8567 this.edit_breakpoint_at_anchor(
8568 anchor,
8569 breakpoint.as_ref().clone(),
8570 BreakpointEditAction::Toggle,
8571 cx,
8572 );
8573 })
8574 .log_err();
8575 }
8576 })
8577 .entry(log_breakpoint_msg, None, {
8578 let breakpoint = breakpoint.clone();
8579 let weak_editor = weak_editor.clone();
8580 move |window, cx| {
8581 weak_editor
8582 .update(cx, |this, cx| {
8583 this.add_edit_breakpoint_block(
8584 anchor,
8585 breakpoint.as_ref(),
8586 BreakpointPromptEditAction::Log,
8587 window,
8588 cx,
8589 );
8590 })
8591 .log_err();
8592 }
8593 })
8594 .entry(condition_breakpoint_msg, None, {
8595 let breakpoint = breakpoint.clone();
8596 let weak_editor = weak_editor.clone();
8597 move |window, cx| {
8598 weak_editor
8599 .update(cx, |this, cx| {
8600 this.add_edit_breakpoint_block(
8601 anchor,
8602 breakpoint.as_ref(),
8603 BreakpointPromptEditAction::Condition,
8604 window,
8605 cx,
8606 );
8607 })
8608 .log_err();
8609 }
8610 })
8611 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8612 weak_editor
8613 .update(cx, |this, cx| {
8614 this.add_edit_breakpoint_block(
8615 anchor,
8616 breakpoint.as_ref(),
8617 BreakpointPromptEditAction::HitCondition,
8618 window,
8619 cx,
8620 );
8621 })
8622 .log_err();
8623 })
8624 })
8625 }
8626
8627 fn render_breakpoint(
8628 &self,
8629 position: Anchor,
8630 row: DisplayRow,
8631 breakpoint: &Breakpoint,
8632 state: Option<BreakpointSessionState>,
8633 cx: &mut Context<Self>,
8634 ) -> IconButton {
8635 let is_rejected = state.is_some_and(|s| !s.verified);
8636 // Is it a breakpoint that shows up when hovering over gutter?
8637 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8638 (false, false),
8639 |PhantomBreakpointIndicator {
8640 is_active,
8641 display_row,
8642 collides_with_existing_breakpoint,
8643 }| {
8644 (
8645 is_active && display_row == row,
8646 collides_with_existing_breakpoint,
8647 )
8648 },
8649 );
8650
8651 let (color, icon) = {
8652 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8653 (false, false) => ui::IconName::DebugBreakpoint,
8654 (true, false) => ui::IconName::DebugLogBreakpoint,
8655 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8656 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8657 };
8658
8659 let theme_colors = cx.theme().colors();
8660
8661 let color = if is_phantom {
8662 if collides_with_existing {
8663 Color::Custom(
8664 theme_colors
8665 .debugger_accent
8666 .blend(theme_colors.text.opacity(0.6)),
8667 )
8668 } else {
8669 Color::Hint
8670 }
8671 } else if is_rejected {
8672 Color::Disabled
8673 } else {
8674 Color::Debugger
8675 };
8676
8677 (color, icon)
8678 };
8679
8680 let breakpoint = Arc::from(breakpoint.clone());
8681
8682 let alt_as_text = gpui::Keystroke {
8683 modifiers: Modifiers::secondary_key(),
8684 ..Default::default()
8685 };
8686 let primary_action_text = if breakpoint.is_disabled() {
8687 "Enable breakpoint"
8688 } else if is_phantom && !collides_with_existing {
8689 "Set breakpoint"
8690 } else {
8691 "Unset breakpoint"
8692 };
8693 let focus_handle = self.focus_handle.clone();
8694
8695 let meta = if is_rejected {
8696 SharedString::from("No executable code is associated with this line.")
8697 } else if collides_with_existing && !breakpoint.is_disabled() {
8698 SharedString::from(format!(
8699 "{alt_as_text}-click to disable,\nright-click for more options."
8700 ))
8701 } else {
8702 SharedString::from("Right-click for more options.")
8703 };
8704 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8705 .icon_size(IconSize::XSmall)
8706 .size(ui::ButtonSize::None)
8707 .when(is_rejected, |this| {
8708 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8709 })
8710 .icon_color(color)
8711 .style(ButtonStyle::Transparent)
8712 .on_click(cx.listener({
8713 move |editor, event: &ClickEvent, window, cx| {
8714 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8715 BreakpointEditAction::InvertState
8716 } else {
8717 BreakpointEditAction::Toggle
8718 };
8719
8720 window.focus(&editor.focus_handle(cx), cx);
8721 editor.edit_breakpoint_at_anchor(
8722 position,
8723 breakpoint.as_ref().clone(),
8724 edit_action,
8725 cx,
8726 );
8727 }
8728 }))
8729 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8730 editor.set_breakpoint_context_menu(
8731 row,
8732 Some(position),
8733 event.position(),
8734 window,
8735 cx,
8736 );
8737 }))
8738 .tooltip(move |_window, cx| {
8739 Tooltip::with_meta_in(
8740 primary_action_text,
8741 Some(&ToggleBreakpoint),
8742 meta.clone(),
8743 &focus_handle,
8744 cx,
8745 )
8746 })
8747 }
8748
8749 fn build_tasks_context(
8750 project: &Entity<Project>,
8751 buffer: &Entity<Buffer>,
8752 buffer_row: u32,
8753 tasks: &Arc<RunnableTasks>,
8754 cx: &mut Context<Self>,
8755 ) -> Task<Option<task::TaskContext>> {
8756 let position = Point::new(buffer_row, tasks.column);
8757 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8758 let location = Location {
8759 buffer: buffer.clone(),
8760 range: range_start..range_start,
8761 };
8762 // Fill in the environmental variables from the tree-sitter captures
8763 let mut captured_task_variables = TaskVariables::default();
8764 for (capture_name, value) in tasks.extra_variables.clone() {
8765 captured_task_variables.insert(
8766 task::VariableName::Custom(capture_name.into()),
8767 value.clone(),
8768 );
8769 }
8770 project.update(cx, |project, cx| {
8771 project.task_store().update(cx, |task_store, cx| {
8772 task_store.task_context_for_location(captured_task_variables, location, cx)
8773 })
8774 })
8775 }
8776
8777 pub fn spawn_nearest_task(
8778 &mut self,
8779 action: &SpawnNearestTask,
8780 window: &mut Window,
8781 cx: &mut Context<Self>,
8782 ) {
8783 let Some((workspace, _)) = self.workspace.clone() else {
8784 return;
8785 };
8786 let Some(project) = self.project.clone() else {
8787 return;
8788 };
8789
8790 // Try to find a closest, enclosing node using tree-sitter that has a task
8791 let Some((buffer, buffer_row, tasks)) = self
8792 .find_enclosing_node_task(cx)
8793 // Or find the task that's closest in row-distance.
8794 .or_else(|| self.find_closest_task(cx))
8795 else {
8796 return;
8797 };
8798
8799 let reveal_strategy = action.reveal;
8800 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8801 cx.spawn_in(window, async move |_, cx| {
8802 let context = task_context.await?;
8803 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8804
8805 let resolved = &mut resolved_task.resolved;
8806 resolved.reveal = reveal_strategy;
8807
8808 workspace
8809 .update_in(cx, |workspace, window, cx| {
8810 workspace.schedule_resolved_task(
8811 task_source_kind,
8812 resolved_task,
8813 false,
8814 window,
8815 cx,
8816 );
8817 })
8818 .ok()
8819 })
8820 .detach();
8821 }
8822
8823 fn find_closest_task(
8824 &mut self,
8825 cx: &mut Context<Self>,
8826 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8827 let cursor_row = self
8828 .selections
8829 .newest_adjusted(&self.display_snapshot(cx))
8830 .head()
8831 .row;
8832
8833 let ((buffer_id, row), tasks) = self
8834 .tasks
8835 .iter()
8836 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8837
8838 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8839 let tasks = Arc::new(tasks.to_owned());
8840 Some((buffer, *row, tasks))
8841 }
8842
8843 fn find_enclosing_node_task(
8844 &mut self,
8845 cx: &mut Context<Self>,
8846 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8847 let snapshot = self.buffer.read(cx).snapshot(cx);
8848 let offset = self
8849 .selections
8850 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8851 .head();
8852 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8853 let offset = excerpt.map_offset_to_buffer(offset);
8854 let buffer_id = excerpt.buffer().remote_id();
8855
8856 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8857 let mut cursor = layer.node().walk();
8858
8859 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8860 if cursor.node().end_byte() == offset.0 {
8861 cursor.goto_next_sibling();
8862 }
8863 }
8864
8865 // Ascend to the smallest ancestor that contains the range and has a task.
8866 loop {
8867 let node = cursor.node();
8868 let node_range = node.byte_range();
8869 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8870
8871 // Check if this node contains our offset
8872 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8873 // If it contains offset, check for task
8874 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8875 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8876 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8877 }
8878 }
8879
8880 if !cursor.goto_parent() {
8881 break;
8882 }
8883 }
8884 None
8885 }
8886
8887 fn render_run_indicator(
8888 &self,
8889 _style: &EditorStyle,
8890 is_active: bool,
8891 row: DisplayRow,
8892 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8893 cx: &mut Context<Self>,
8894 ) -> IconButton {
8895 let color = Color::Muted;
8896 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8897
8898 IconButton::new(
8899 ("run_indicator", row.0 as usize),
8900 ui::IconName::PlayOutlined,
8901 )
8902 .shape(ui::IconButtonShape::Square)
8903 .icon_size(IconSize::XSmall)
8904 .icon_color(color)
8905 .toggle_state(is_active)
8906 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8907 let quick_launch = match e {
8908 ClickEvent::Keyboard(_) => true,
8909 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8910 };
8911
8912 window.focus(&editor.focus_handle(cx), cx);
8913 editor.toggle_code_actions(
8914 &ToggleCodeActions {
8915 deployed_from: Some(CodeActionSource::RunMenu(row)),
8916 quick_launch,
8917 },
8918 window,
8919 cx,
8920 );
8921 }))
8922 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8923 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8924 }))
8925 }
8926
8927 pub fn context_menu_visible(&self) -> bool {
8928 !self.edit_prediction_preview_is_active()
8929 && self
8930 .context_menu
8931 .borrow()
8932 .as_ref()
8933 .is_some_and(|menu| menu.visible())
8934 }
8935
8936 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8937 self.context_menu
8938 .borrow()
8939 .as_ref()
8940 .map(|menu| menu.origin())
8941 }
8942
8943 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8944 self.context_menu_options = Some(options);
8945 }
8946
8947 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8948 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8949
8950 fn render_edit_prediction_popover(
8951 &mut self,
8952 text_bounds: &Bounds<Pixels>,
8953 content_origin: gpui::Point<Pixels>,
8954 right_margin: Pixels,
8955 editor_snapshot: &EditorSnapshot,
8956 visible_row_range: Range<DisplayRow>,
8957 scroll_top: ScrollOffset,
8958 scroll_bottom: ScrollOffset,
8959 line_layouts: &[LineWithInvisibles],
8960 line_height: Pixels,
8961 scroll_position: gpui::Point<ScrollOffset>,
8962 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8963 newest_selection_head: Option<DisplayPoint>,
8964 editor_width: Pixels,
8965 style: &EditorStyle,
8966 window: &mut Window,
8967 cx: &mut App,
8968 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8969 if self.mode().is_minimap() {
8970 return None;
8971 }
8972 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8973
8974 if self.edit_prediction_visible_in_cursor_popover(true) {
8975 return None;
8976 }
8977
8978 match &active_edit_prediction.completion {
8979 EditPrediction::MoveWithin { target, .. } => {
8980 let target_display_point = target.to_display_point(editor_snapshot);
8981
8982 if self.edit_prediction_requires_modifier() {
8983 if !self.edit_prediction_preview_is_active() {
8984 return None;
8985 }
8986
8987 self.render_edit_prediction_modifier_jump_popover(
8988 text_bounds,
8989 content_origin,
8990 visible_row_range,
8991 line_layouts,
8992 line_height,
8993 scroll_pixel_position,
8994 newest_selection_head,
8995 target_display_point,
8996 window,
8997 cx,
8998 )
8999 } else {
9000 self.render_edit_prediction_eager_jump_popover(
9001 text_bounds,
9002 content_origin,
9003 editor_snapshot,
9004 visible_row_range,
9005 scroll_top,
9006 scroll_bottom,
9007 line_height,
9008 scroll_pixel_position,
9009 target_display_point,
9010 editor_width,
9011 window,
9012 cx,
9013 )
9014 }
9015 }
9016 EditPrediction::Edit {
9017 display_mode: EditDisplayMode::Inline,
9018 ..
9019 } => None,
9020 EditPrediction::Edit {
9021 display_mode: EditDisplayMode::TabAccept,
9022 edits,
9023 ..
9024 } => {
9025 let range = &edits.first()?.0;
9026 let target_display_point = range.end.to_display_point(editor_snapshot);
9027
9028 self.render_edit_prediction_end_of_line_popover(
9029 "Accept",
9030 editor_snapshot,
9031 visible_row_range,
9032 target_display_point,
9033 line_height,
9034 scroll_pixel_position,
9035 content_origin,
9036 editor_width,
9037 window,
9038 cx,
9039 )
9040 }
9041 EditPrediction::Edit {
9042 edits,
9043 edit_preview,
9044 display_mode: EditDisplayMode::DiffPopover,
9045 snapshot,
9046 } => self.render_edit_prediction_diff_popover(
9047 text_bounds,
9048 content_origin,
9049 right_margin,
9050 editor_snapshot,
9051 visible_row_range,
9052 line_layouts,
9053 line_height,
9054 scroll_position,
9055 scroll_pixel_position,
9056 newest_selection_head,
9057 editor_width,
9058 style,
9059 edits,
9060 edit_preview,
9061 snapshot,
9062 window,
9063 cx,
9064 ),
9065 EditPrediction::MoveOutside { snapshot, .. } => {
9066 let mut element = self
9067 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9068 .into_any();
9069
9070 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9071 let origin_x = text_bounds.size.width - size.width - px(30.);
9072 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9073 element.prepaint_at(origin, window, cx);
9074
9075 Some((element, origin))
9076 }
9077 }
9078 }
9079
9080 fn render_edit_prediction_modifier_jump_popover(
9081 &mut self,
9082 text_bounds: &Bounds<Pixels>,
9083 content_origin: gpui::Point<Pixels>,
9084 visible_row_range: Range<DisplayRow>,
9085 line_layouts: &[LineWithInvisibles],
9086 line_height: Pixels,
9087 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9088 newest_selection_head: Option<DisplayPoint>,
9089 target_display_point: DisplayPoint,
9090 window: &mut Window,
9091 cx: &mut App,
9092 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9093 let scrolled_content_origin =
9094 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9095
9096 const SCROLL_PADDING_Y: Pixels = px(12.);
9097
9098 if target_display_point.row() < visible_row_range.start {
9099 return self.render_edit_prediction_scroll_popover(
9100 |_| SCROLL_PADDING_Y,
9101 IconName::ArrowUp,
9102 visible_row_range,
9103 line_layouts,
9104 newest_selection_head,
9105 scrolled_content_origin,
9106 window,
9107 cx,
9108 );
9109 } else if target_display_point.row() >= visible_row_range.end {
9110 return self.render_edit_prediction_scroll_popover(
9111 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9112 IconName::ArrowDown,
9113 visible_row_range,
9114 line_layouts,
9115 newest_selection_head,
9116 scrolled_content_origin,
9117 window,
9118 cx,
9119 );
9120 }
9121
9122 const POLE_WIDTH: Pixels = px(2.);
9123
9124 let line_layout =
9125 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9126 let target_column = target_display_point.column() as usize;
9127
9128 let target_x = line_layout.x_for_index(target_column);
9129 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9130 - scroll_pixel_position.y;
9131
9132 let flag_on_right = target_x < text_bounds.size.width / 2.;
9133
9134 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9135 border_color.l += 0.001;
9136
9137 let mut element = v_flex()
9138 .items_end()
9139 .when(flag_on_right, |el| el.items_start())
9140 .child(if flag_on_right {
9141 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9142 .rounded_bl(px(0.))
9143 .rounded_tl(px(0.))
9144 .border_l_2()
9145 .border_color(border_color)
9146 } else {
9147 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9148 .rounded_br(px(0.))
9149 .rounded_tr(px(0.))
9150 .border_r_2()
9151 .border_color(border_color)
9152 })
9153 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9154 .into_any();
9155
9156 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9157
9158 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9159 - point(
9160 if flag_on_right {
9161 POLE_WIDTH
9162 } else {
9163 size.width - POLE_WIDTH
9164 },
9165 size.height - line_height,
9166 );
9167
9168 origin.x = origin.x.max(content_origin.x);
9169
9170 element.prepaint_at(origin, window, cx);
9171
9172 Some((element, origin))
9173 }
9174
9175 fn render_edit_prediction_scroll_popover(
9176 &mut self,
9177 to_y: impl Fn(Size<Pixels>) -> Pixels,
9178 scroll_icon: IconName,
9179 visible_row_range: Range<DisplayRow>,
9180 line_layouts: &[LineWithInvisibles],
9181 newest_selection_head: Option<DisplayPoint>,
9182 scrolled_content_origin: gpui::Point<Pixels>,
9183 window: &mut Window,
9184 cx: &mut App,
9185 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9186 let mut element = self
9187 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9188 .into_any();
9189
9190 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9191
9192 let cursor = newest_selection_head?;
9193 let cursor_row_layout =
9194 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9195 let cursor_column = cursor.column() as usize;
9196
9197 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9198
9199 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9200
9201 element.prepaint_at(origin, window, cx);
9202 Some((element, origin))
9203 }
9204
9205 fn render_edit_prediction_eager_jump_popover(
9206 &mut self,
9207 text_bounds: &Bounds<Pixels>,
9208 content_origin: gpui::Point<Pixels>,
9209 editor_snapshot: &EditorSnapshot,
9210 visible_row_range: Range<DisplayRow>,
9211 scroll_top: ScrollOffset,
9212 scroll_bottom: ScrollOffset,
9213 line_height: Pixels,
9214 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9215 target_display_point: DisplayPoint,
9216 editor_width: Pixels,
9217 window: &mut Window,
9218 cx: &mut App,
9219 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9220 if target_display_point.row().as_f64() < scroll_top {
9221 let mut element = self
9222 .render_edit_prediction_line_popover(
9223 "Jump to Edit",
9224 Some(IconName::ArrowUp),
9225 window,
9226 cx,
9227 )
9228 .into_any();
9229
9230 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9231 let offset = point(
9232 (text_bounds.size.width - size.width) / 2.,
9233 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9234 );
9235
9236 let origin = text_bounds.origin + offset;
9237 element.prepaint_at(origin, window, cx);
9238 Some((element, origin))
9239 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9240 let mut element = self
9241 .render_edit_prediction_line_popover(
9242 "Jump to Edit",
9243 Some(IconName::ArrowDown),
9244 window,
9245 cx,
9246 )
9247 .into_any();
9248
9249 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9250 let offset = point(
9251 (text_bounds.size.width - size.width) / 2.,
9252 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9253 );
9254
9255 let origin = text_bounds.origin + offset;
9256 element.prepaint_at(origin, window, cx);
9257 Some((element, origin))
9258 } else {
9259 self.render_edit_prediction_end_of_line_popover(
9260 "Jump to Edit",
9261 editor_snapshot,
9262 visible_row_range,
9263 target_display_point,
9264 line_height,
9265 scroll_pixel_position,
9266 content_origin,
9267 editor_width,
9268 window,
9269 cx,
9270 )
9271 }
9272 }
9273
9274 fn render_edit_prediction_end_of_line_popover(
9275 self: &mut Editor,
9276 label: &'static str,
9277 editor_snapshot: &EditorSnapshot,
9278 visible_row_range: Range<DisplayRow>,
9279 target_display_point: DisplayPoint,
9280 line_height: Pixels,
9281 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9282 content_origin: gpui::Point<Pixels>,
9283 editor_width: Pixels,
9284 window: &mut Window,
9285 cx: &mut App,
9286 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9287 let target_line_end = DisplayPoint::new(
9288 target_display_point.row(),
9289 editor_snapshot.line_len(target_display_point.row()),
9290 );
9291
9292 let mut element = self
9293 .render_edit_prediction_line_popover(label, None, window, cx)
9294 .into_any();
9295
9296 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9297
9298 let line_origin =
9299 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9300
9301 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9302 let mut origin = start_point
9303 + line_origin
9304 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9305 origin.x = origin.x.max(content_origin.x);
9306
9307 let max_x = content_origin.x + editor_width - size.width;
9308
9309 if origin.x > max_x {
9310 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9311
9312 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9313 origin.y += offset;
9314 IconName::ArrowUp
9315 } else {
9316 origin.y -= offset;
9317 IconName::ArrowDown
9318 };
9319
9320 element = self
9321 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9322 .into_any();
9323
9324 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9325
9326 origin.x = content_origin.x + editor_width - size.width - px(2.);
9327 }
9328
9329 element.prepaint_at(origin, window, cx);
9330 Some((element, origin))
9331 }
9332
9333 fn render_edit_prediction_diff_popover(
9334 self: &Editor,
9335 text_bounds: &Bounds<Pixels>,
9336 content_origin: gpui::Point<Pixels>,
9337 right_margin: Pixels,
9338 editor_snapshot: &EditorSnapshot,
9339 visible_row_range: Range<DisplayRow>,
9340 line_layouts: &[LineWithInvisibles],
9341 line_height: Pixels,
9342 scroll_position: gpui::Point<ScrollOffset>,
9343 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9344 newest_selection_head: Option<DisplayPoint>,
9345 editor_width: Pixels,
9346 style: &EditorStyle,
9347 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9348 edit_preview: &Option<language::EditPreview>,
9349 snapshot: &language::BufferSnapshot,
9350 window: &mut Window,
9351 cx: &mut App,
9352 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9353 let edit_start = edits
9354 .first()
9355 .unwrap()
9356 .0
9357 .start
9358 .to_display_point(editor_snapshot);
9359 let edit_end = edits
9360 .last()
9361 .unwrap()
9362 .0
9363 .end
9364 .to_display_point(editor_snapshot);
9365
9366 let is_visible = visible_row_range.contains(&edit_start.row())
9367 || visible_row_range.contains(&edit_end.row());
9368 if !is_visible {
9369 return None;
9370 }
9371
9372 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9373 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9374 } else {
9375 // Fallback for providers without edit_preview
9376 crate::edit_prediction_fallback_text(edits, cx)
9377 };
9378
9379 let styled_text = highlighted_edits.to_styled_text(&style.text);
9380 let line_count = highlighted_edits.text.lines().count();
9381
9382 const BORDER_WIDTH: Pixels = px(1.);
9383
9384 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9385 let has_keybind = keybind.is_some();
9386
9387 let mut element = h_flex()
9388 .items_start()
9389 .child(
9390 h_flex()
9391 .bg(cx.theme().colors().editor_background)
9392 .border(BORDER_WIDTH)
9393 .shadow_xs()
9394 .border_color(cx.theme().colors().border)
9395 .rounded_l_lg()
9396 .when(line_count > 1, |el| el.rounded_br_lg())
9397 .pr_1()
9398 .child(styled_text),
9399 )
9400 .child(
9401 h_flex()
9402 .h(line_height + BORDER_WIDTH * 2.)
9403 .px_1p5()
9404 .gap_1()
9405 // Workaround: For some reason, there's a gap if we don't do this
9406 .ml(-BORDER_WIDTH)
9407 .shadow(vec![gpui::BoxShadow {
9408 color: gpui::black().opacity(0.05),
9409 offset: point(px(1.), px(1.)),
9410 blur_radius: px(2.),
9411 spread_radius: px(0.),
9412 }])
9413 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9414 .border(BORDER_WIDTH)
9415 .border_color(cx.theme().colors().border)
9416 .rounded_r_lg()
9417 .id("edit_prediction_diff_popover_keybind")
9418 .when(!has_keybind, |el| {
9419 let status_colors = cx.theme().status();
9420
9421 el.bg(status_colors.error_background)
9422 .border_color(status_colors.error.opacity(0.6))
9423 .child(Icon::new(IconName::Info).color(Color::Error))
9424 .cursor_default()
9425 .hoverable_tooltip(move |_window, cx| {
9426 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9427 })
9428 })
9429 .children(keybind),
9430 )
9431 .into_any();
9432
9433 let longest_row =
9434 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9435 let longest_line_width = if visible_row_range.contains(&longest_row) {
9436 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9437 } else {
9438 layout_line(
9439 longest_row,
9440 editor_snapshot,
9441 style,
9442 editor_width,
9443 |_| false,
9444 window,
9445 cx,
9446 )
9447 .width
9448 };
9449
9450 let viewport_bounds =
9451 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9452 right: -right_margin,
9453 ..Default::default()
9454 });
9455
9456 let x_after_longest = Pixels::from(
9457 ScrollPixelOffset::from(
9458 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9459 ) - scroll_pixel_position.x,
9460 );
9461
9462 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9463
9464 // Fully visible if it can be displayed within the window (allow overlapping other
9465 // panes). However, this is only allowed if the popover starts within text_bounds.
9466 let can_position_to_the_right = x_after_longest < text_bounds.right()
9467 && x_after_longest + element_bounds.width < viewport_bounds.right();
9468
9469 let mut origin = if can_position_to_the_right {
9470 point(
9471 x_after_longest,
9472 text_bounds.origin.y
9473 + Pixels::from(
9474 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9475 - scroll_pixel_position.y,
9476 ),
9477 )
9478 } else {
9479 let cursor_row = newest_selection_head.map(|head| head.row());
9480 let above_edit = edit_start
9481 .row()
9482 .0
9483 .checked_sub(line_count as u32)
9484 .map(DisplayRow);
9485 let below_edit = Some(edit_end.row() + 1);
9486 let above_cursor =
9487 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9488 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9489
9490 // Place the edit popover adjacent to the edit if there is a location
9491 // available that is onscreen and does not obscure the cursor. Otherwise,
9492 // place it adjacent to the cursor.
9493 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9494 .into_iter()
9495 .flatten()
9496 .find(|&start_row| {
9497 let end_row = start_row + line_count as u32;
9498 visible_row_range.contains(&start_row)
9499 && visible_row_range.contains(&end_row)
9500 && cursor_row
9501 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9502 })?;
9503
9504 content_origin
9505 + point(
9506 Pixels::from(-scroll_pixel_position.x),
9507 Pixels::from(
9508 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9509 ),
9510 )
9511 };
9512
9513 origin.x -= BORDER_WIDTH;
9514
9515 window.defer_draw(element, origin, 1);
9516
9517 // Do not return an element, since it will already be drawn due to defer_draw.
9518 None
9519 }
9520
9521 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9522 px(30.)
9523 }
9524
9525 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9526 if self.read_only(cx) {
9527 cx.theme().players().read_only()
9528 } else {
9529 self.style.as_ref().unwrap().local_player
9530 }
9531 }
9532
9533 fn render_edit_prediction_accept_keybind(
9534 &self,
9535 window: &mut Window,
9536 cx: &mut App,
9537 ) -> Option<AnyElement> {
9538 let accept_binding =
9539 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9540 let accept_keystroke = accept_binding.keystroke()?;
9541
9542 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9543
9544 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9545 Color::Accent
9546 } else {
9547 Color::Muted
9548 };
9549
9550 h_flex()
9551 .px_0p5()
9552 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9553 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9554 .text_size(TextSize::XSmall.rems(cx))
9555 .child(h_flex().children(ui::render_modifiers(
9556 accept_keystroke.modifiers(),
9557 PlatformStyle::platform(),
9558 Some(modifiers_color),
9559 Some(IconSize::XSmall.rems().into()),
9560 true,
9561 )))
9562 .when(is_platform_style_mac, |parent| {
9563 parent.child(accept_keystroke.key().to_string())
9564 })
9565 .when(!is_platform_style_mac, |parent| {
9566 parent.child(
9567 Key::new(
9568 util::capitalize(accept_keystroke.key()),
9569 Some(Color::Default),
9570 )
9571 .size(Some(IconSize::XSmall.rems().into())),
9572 )
9573 })
9574 .into_any()
9575 .into()
9576 }
9577
9578 fn render_edit_prediction_line_popover(
9579 &self,
9580 label: impl Into<SharedString>,
9581 icon: Option<IconName>,
9582 window: &mut Window,
9583 cx: &mut App,
9584 ) -> Stateful<Div> {
9585 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9586
9587 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9588 let has_keybind = keybind.is_some();
9589
9590 h_flex()
9591 .id("ep-line-popover")
9592 .py_0p5()
9593 .pl_1()
9594 .pr(padding_right)
9595 .gap_1()
9596 .rounded_md()
9597 .border_1()
9598 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9599 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9600 .shadow_xs()
9601 .when(!has_keybind, |el| {
9602 let status_colors = cx.theme().status();
9603
9604 el.bg(status_colors.error_background)
9605 .border_color(status_colors.error.opacity(0.6))
9606 .pl_2()
9607 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9608 .cursor_default()
9609 .hoverable_tooltip(move |_window, cx| {
9610 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9611 })
9612 })
9613 .children(keybind)
9614 .child(
9615 Label::new(label)
9616 .size(LabelSize::Small)
9617 .when(!has_keybind, |el| {
9618 el.color(cx.theme().status().error.into()).strikethrough()
9619 }),
9620 )
9621 .when(!has_keybind, |el| {
9622 el.child(
9623 h_flex().ml_1().child(
9624 Icon::new(IconName::Info)
9625 .size(IconSize::Small)
9626 .color(cx.theme().status().error.into()),
9627 ),
9628 )
9629 })
9630 .when_some(icon, |element, icon| {
9631 element.child(
9632 div()
9633 .mt(px(1.5))
9634 .child(Icon::new(icon).size(IconSize::Small)),
9635 )
9636 })
9637 }
9638
9639 fn render_edit_prediction_jump_outside_popover(
9640 &self,
9641 snapshot: &BufferSnapshot,
9642 window: &mut Window,
9643 cx: &mut App,
9644 ) -> Stateful<Div> {
9645 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9646 let has_keybind = keybind.is_some();
9647
9648 let file_name = snapshot
9649 .file()
9650 .map(|file| SharedString::new(file.file_name(cx)))
9651 .unwrap_or(SharedString::new_static("untitled"));
9652
9653 h_flex()
9654 .id("ep-jump-outside-popover")
9655 .py_1()
9656 .px_2()
9657 .gap_1()
9658 .rounded_md()
9659 .border_1()
9660 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9661 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9662 .shadow_xs()
9663 .when(!has_keybind, |el| {
9664 let status_colors = cx.theme().status();
9665
9666 el.bg(status_colors.error_background)
9667 .border_color(status_colors.error.opacity(0.6))
9668 .pl_2()
9669 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9670 .cursor_default()
9671 .hoverable_tooltip(move |_window, cx| {
9672 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9673 })
9674 })
9675 .children(keybind)
9676 .child(
9677 Label::new(file_name)
9678 .size(LabelSize::Small)
9679 .buffer_font(cx)
9680 .when(!has_keybind, |el| {
9681 el.color(cx.theme().status().error.into()).strikethrough()
9682 }),
9683 )
9684 .when(!has_keybind, |el| {
9685 el.child(
9686 h_flex().ml_1().child(
9687 Icon::new(IconName::Info)
9688 .size(IconSize::Small)
9689 .color(cx.theme().status().error.into()),
9690 ),
9691 )
9692 })
9693 .child(
9694 div()
9695 .mt(px(1.5))
9696 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9697 )
9698 }
9699
9700 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9701 let accent_color = cx.theme().colors().text_accent;
9702 let editor_bg_color = cx.theme().colors().editor_background;
9703 editor_bg_color.blend(accent_color.opacity(0.1))
9704 }
9705
9706 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9707 let accent_color = cx.theme().colors().text_accent;
9708 let editor_bg_color = cx.theme().colors().editor_background;
9709 editor_bg_color.blend(accent_color.opacity(0.6))
9710 }
9711 fn get_prediction_provider_icon_name(
9712 provider: &Option<RegisteredEditPredictionDelegate>,
9713 ) -> IconName {
9714 match provider {
9715 Some(provider) => match provider.provider.name() {
9716 "copilot" => IconName::Copilot,
9717 "supermaven" => IconName::Supermaven,
9718 _ => IconName::ZedPredict,
9719 },
9720 None => IconName::ZedPredict,
9721 }
9722 }
9723
9724 fn render_edit_prediction_cursor_popover(
9725 &self,
9726 min_width: Pixels,
9727 max_width: Pixels,
9728 cursor_point: Point,
9729 style: &EditorStyle,
9730 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9731 _window: &Window,
9732 cx: &mut Context<Editor>,
9733 ) -> Option<AnyElement> {
9734 let provider = self.edit_prediction_provider.as_ref()?;
9735 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9736
9737 let is_refreshing = provider.provider.is_refreshing(cx);
9738
9739 fn pending_completion_container(icon: IconName) -> Div {
9740 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9741 }
9742
9743 let completion = match &self.active_edit_prediction {
9744 Some(prediction) => {
9745 if !self.has_visible_completions_menu() {
9746 const RADIUS: Pixels = px(6.);
9747 const BORDER_WIDTH: Pixels = px(1.);
9748
9749 return Some(
9750 h_flex()
9751 .elevation_2(cx)
9752 .border(BORDER_WIDTH)
9753 .border_color(cx.theme().colors().border)
9754 .when(accept_keystroke.is_none(), |el| {
9755 el.border_color(cx.theme().status().error)
9756 })
9757 .rounded(RADIUS)
9758 .rounded_tl(px(0.))
9759 .overflow_hidden()
9760 .child(div().px_1p5().child(match &prediction.completion {
9761 EditPrediction::MoveWithin { target, snapshot } => {
9762 use text::ToPoint as _;
9763 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9764 {
9765 Icon::new(IconName::ZedPredictDown)
9766 } else {
9767 Icon::new(IconName::ZedPredictUp)
9768 }
9769 }
9770 EditPrediction::MoveOutside { .. } => {
9771 // TODO [zeta2] custom icon for external jump?
9772 Icon::new(provider_icon)
9773 }
9774 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9775 }))
9776 .child(
9777 h_flex()
9778 .gap_1()
9779 .py_1()
9780 .px_2()
9781 .rounded_r(RADIUS - BORDER_WIDTH)
9782 .border_l_1()
9783 .border_color(cx.theme().colors().border)
9784 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9785 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9786 el.child(
9787 Label::new("Hold")
9788 .size(LabelSize::Small)
9789 .when(accept_keystroke.is_none(), |el| {
9790 el.strikethrough()
9791 })
9792 .line_height_style(LineHeightStyle::UiLabel),
9793 )
9794 })
9795 .id("edit_prediction_cursor_popover_keybind")
9796 .when(accept_keystroke.is_none(), |el| {
9797 let status_colors = cx.theme().status();
9798
9799 el.bg(status_colors.error_background)
9800 .border_color(status_colors.error.opacity(0.6))
9801 .child(Icon::new(IconName::Info).color(Color::Error))
9802 .cursor_default()
9803 .hoverable_tooltip(move |_window, cx| {
9804 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9805 .into()
9806 })
9807 })
9808 .when_some(
9809 accept_keystroke.as_ref(),
9810 |el, accept_keystroke| {
9811 el.child(h_flex().children(ui::render_modifiers(
9812 accept_keystroke.modifiers(),
9813 PlatformStyle::platform(),
9814 Some(Color::Default),
9815 Some(IconSize::XSmall.rems().into()),
9816 false,
9817 )))
9818 },
9819 ),
9820 )
9821 .into_any(),
9822 );
9823 }
9824
9825 self.render_edit_prediction_cursor_popover_preview(
9826 prediction,
9827 cursor_point,
9828 style,
9829 cx,
9830 )?
9831 }
9832
9833 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9834 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9835 stale_completion,
9836 cursor_point,
9837 style,
9838 cx,
9839 )?,
9840
9841 None => pending_completion_container(provider_icon)
9842 .child(Label::new("...").size(LabelSize::Small)),
9843 },
9844
9845 None => pending_completion_container(provider_icon)
9846 .child(Label::new("...").size(LabelSize::Small)),
9847 };
9848
9849 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9850 completion
9851 .with_animation(
9852 "loading-completion",
9853 Animation::new(Duration::from_secs(2))
9854 .repeat()
9855 .with_easing(pulsating_between(0.4, 0.8)),
9856 |label, delta| label.opacity(delta),
9857 )
9858 .into_any_element()
9859 } else {
9860 completion.into_any_element()
9861 };
9862
9863 let has_completion = self.active_edit_prediction.is_some();
9864
9865 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9866 Some(
9867 h_flex()
9868 .min_w(min_width)
9869 .max_w(max_width)
9870 .flex_1()
9871 .elevation_2(cx)
9872 .border_color(cx.theme().colors().border)
9873 .child(
9874 div()
9875 .flex_1()
9876 .py_1()
9877 .px_2()
9878 .overflow_hidden()
9879 .child(completion),
9880 )
9881 .when_some(accept_keystroke, |el, accept_keystroke| {
9882 if !accept_keystroke.modifiers().modified() {
9883 return el;
9884 }
9885
9886 el.child(
9887 h_flex()
9888 .h_full()
9889 .border_l_1()
9890 .rounded_r_lg()
9891 .border_color(cx.theme().colors().border)
9892 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9893 .gap_1()
9894 .py_1()
9895 .px_2()
9896 .child(
9897 h_flex()
9898 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9899 .when(is_platform_style_mac, |parent| parent.gap_1())
9900 .child(h_flex().children(ui::render_modifiers(
9901 accept_keystroke.modifiers(),
9902 PlatformStyle::platform(),
9903 Some(if !has_completion {
9904 Color::Muted
9905 } else {
9906 Color::Default
9907 }),
9908 None,
9909 false,
9910 ))),
9911 )
9912 .child(Label::new("Preview").into_any_element())
9913 .opacity(if has_completion { 1.0 } else { 0.4 }),
9914 )
9915 })
9916 .into_any(),
9917 )
9918 }
9919
9920 fn render_edit_prediction_cursor_popover_preview(
9921 &self,
9922 completion: &EditPredictionState,
9923 cursor_point: Point,
9924 style: &EditorStyle,
9925 cx: &mut Context<Editor>,
9926 ) -> Option<Div> {
9927 use text::ToPoint as _;
9928
9929 fn render_relative_row_jump(
9930 prefix: impl Into<String>,
9931 current_row: u32,
9932 target_row: u32,
9933 ) -> Div {
9934 let (row_diff, arrow) = if target_row < current_row {
9935 (current_row - target_row, IconName::ArrowUp)
9936 } else {
9937 (target_row - current_row, IconName::ArrowDown)
9938 };
9939
9940 h_flex()
9941 .child(
9942 Label::new(format!("{}{}", prefix.into(), row_diff))
9943 .color(Color::Muted)
9944 .size(LabelSize::Small),
9945 )
9946 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9947 }
9948
9949 let supports_jump = self
9950 .edit_prediction_provider
9951 .as_ref()
9952 .map(|provider| provider.provider.supports_jump_to_edit())
9953 .unwrap_or(true);
9954
9955 match &completion.completion {
9956 EditPrediction::MoveWithin {
9957 target, snapshot, ..
9958 } => {
9959 if !supports_jump {
9960 return None;
9961 }
9962
9963 Some(
9964 h_flex()
9965 .px_2()
9966 .gap_2()
9967 .flex_1()
9968 .child(
9969 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9970 Icon::new(IconName::ZedPredictDown)
9971 } else {
9972 Icon::new(IconName::ZedPredictUp)
9973 },
9974 )
9975 .child(Label::new("Jump to Edit")),
9976 )
9977 }
9978 EditPrediction::MoveOutside { snapshot, .. } => {
9979 let file_name = snapshot
9980 .file()
9981 .map(|file| file.file_name(cx))
9982 .unwrap_or("untitled");
9983 Some(
9984 h_flex()
9985 .px_2()
9986 .gap_2()
9987 .flex_1()
9988 .child(Icon::new(IconName::ZedPredict))
9989 .child(Label::new(format!("Jump to {file_name}"))),
9990 )
9991 }
9992 EditPrediction::Edit {
9993 edits,
9994 edit_preview,
9995 snapshot,
9996 display_mode: _,
9997 } => {
9998 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9999
10000 let (highlighted_edits, has_more_lines) =
10001 if let Some(edit_preview) = edit_preview.as_ref() {
10002 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10003 .first_line_preview()
10004 } else {
10005 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10006 };
10007
10008 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10009 .with_default_highlights(&style.text, highlighted_edits.highlights);
10010
10011 let preview = h_flex()
10012 .gap_1()
10013 .min_w_16()
10014 .child(styled_text)
10015 .when(has_more_lines, |parent| parent.child("…"));
10016
10017 let left = if supports_jump && first_edit_row != cursor_point.row {
10018 render_relative_row_jump("", cursor_point.row, first_edit_row)
10019 .into_any_element()
10020 } else {
10021 let icon_name =
10022 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
10023 Icon::new(icon_name).into_any_element()
10024 };
10025
10026 Some(
10027 h_flex()
10028 .h_full()
10029 .flex_1()
10030 .gap_2()
10031 .pr_1()
10032 .overflow_x_hidden()
10033 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10034 .child(left)
10035 .child(preview),
10036 )
10037 }
10038 }
10039 }
10040
10041 pub fn render_context_menu(
10042 &mut self,
10043 max_height_in_lines: u32,
10044 window: &mut Window,
10045 cx: &mut Context<Editor>,
10046 ) -> Option<AnyElement> {
10047 let menu = self.context_menu.borrow();
10048 let menu = menu.as_ref()?;
10049 if !menu.visible() {
10050 return None;
10051 };
10052 self.style
10053 .as_ref()
10054 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10055 }
10056
10057 fn render_context_menu_aside(
10058 &mut self,
10059 max_size: Size<Pixels>,
10060 window: &mut Window,
10061 cx: &mut Context<Editor>,
10062 ) -> Option<AnyElement> {
10063 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10064 if menu.visible() {
10065 menu.render_aside(max_size, window, cx)
10066 } else {
10067 None
10068 }
10069 })
10070 }
10071
10072 fn hide_context_menu(
10073 &mut self,
10074 window: &mut Window,
10075 cx: &mut Context<Self>,
10076 ) -> Option<CodeContextMenu> {
10077 cx.notify();
10078 self.completion_tasks.clear();
10079 let context_menu = self.context_menu.borrow_mut().take();
10080 self.stale_edit_prediction_in_menu.take();
10081 self.update_visible_edit_prediction(window, cx);
10082 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10083 && let Some(completion_provider) = &self.completion_provider
10084 {
10085 completion_provider.selection_changed(None, window, cx);
10086 }
10087 context_menu
10088 }
10089
10090 fn show_snippet_choices(
10091 &mut self,
10092 choices: &Vec<String>,
10093 selection: Range<Anchor>,
10094 cx: &mut Context<Self>,
10095 ) {
10096 let Some((_, buffer, _)) = self
10097 .buffer()
10098 .read(cx)
10099 .excerpt_containing(selection.start, cx)
10100 else {
10101 return;
10102 };
10103 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10104 else {
10105 return;
10106 };
10107 if buffer != end_buffer {
10108 log::error!("expected anchor range to have matching buffer IDs");
10109 return;
10110 }
10111
10112 let id = post_inc(&mut self.next_completion_id);
10113 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10114 let mut context_menu = self.context_menu.borrow_mut();
10115 let old_menu = context_menu.take();
10116 *context_menu = Some(CodeContextMenu::Completions(
10117 CompletionsMenu::new_snippet_choices(
10118 id,
10119 true,
10120 choices,
10121 selection,
10122 buffer,
10123 old_menu.map(|menu| menu.primary_scroll_handle()),
10124 snippet_sort_order,
10125 ),
10126 ));
10127 }
10128
10129 pub fn insert_snippet(
10130 &mut self,
10131 insertion_ranges: &[Range<MultiBufferOffset>],
10132 snippet: Snippet,
10133 window: &mut Window,
10134 cx: &mut Context<Self>,
10135 ) -> Result<()> {
10136 struct Tabstop<T> {
10137 is_end_tabstop: bool,
10138 ranges: Vec<Range<T>>,
10139 choices: Option<Vec<String>>,
10140 }
10141
10142 let tabstops = self.buffer.update(cx, |buffer, cx| {
10143 let snippet_text: Arc<str> = snippet.text.clone().into();
10144 let edits = insertion_ranges
10145 .iter()
10146 .cloned()
10147 .map(|range| (range, snippet_text.clone()));
10148 let autoindent_mode = AutoindentMode::Block {
10149 original_indent_columns: Vec::new(),
10150 };
10151 buffer.edit(edits, Some(autoindent_mode), cx);
10152
10153 let snapshot = &*buffer.read(cx);
10154 let snippet = &snippet;
10155 snippet
10156 .tabstops
10157 .iter()
10158 .map(|tabstop| {
10159 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10160 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10161 });
10162 let mut tabstop_ranges = tabstop
10163 .ranges
10164 .iter()
10165 .flat_map(|tabstop_range| {
10166 let mut delta = 0_isize;
10167 insertion_ranges.iter().map(move |insertion_range| {
10168 let insertion_start = insertion_range.start + delta;
10169 delta += snippet.text.len() as isize
10170 - (insertion_range.end - insertion_range.start) as isize;
10171
10172 let start =
10173 (insertion_start + tabstop_range.start).min(snapshot.len());
10174 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10175 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10176 })
10177 })
10178 .collect::<Vec<_>>();
10179 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10180
10181 Tabstop {
10182 is_end_tabstop,
10183 ranges: tabstop_ranges,
10184 choices: tabstop.choices.clone(),
10185 }
10186 })
10187 .collect::<Vec<_>>()
10188 });
10189 if let Some(tabstop) = tabstops.first() {
10190 self.change_selections(Default::default(), window, cx, |s| {
10191 // Reverse order so that the first range is the newest created selection.
10192 // Completions will use it and autoscroll will prioritize it.
10193 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10194 });
10195
10196 if let Some(choices) = &tabstop.choices
10197 && let Some(selection) = tabstop.ranges.first()
10198 {
10199 self.show_snippet_choices(choices, selection.clone(), cx)
10200 }
10201
10202 // If we're already at the last tabstop and it's at the end of the snippet,
10203 // we're done, we don't need to keep the state around.
10204 if !tabstop.is_end_tabstop {
10205 let choices = tabstops
10206 .iter()
10207 .map(|tabstop| tabstop.choices.clone())
10208 .collect();
10209
10210 let ranges = tabstops
10211 .into_iter()
10212 .map(|tabstop| tabstop.ranges)
10213 .collect::<Vec<_>>();
10214
10215 self.snippet_stack.push(SnippetState {
10216 active_index: 0,
10217 ranges,
10218 choices,
10219 });
10220 }
10221
10222 // Check whether the just-entered snippet ends with an auto-closable bracket.
10223 if self.autoclose_regions.is_empty() {
10224 let snapshot = self.buffer.read(cx).snapshot(cx);
10225 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10226 let selection_head = selection.head();
10227 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10228 continue;
10229 };
10230
10231 let mut bracket_pair = None;
10232 let max_lookup_length = scope
10233 .brackets()
10234 .map(|(pair, _)| {
10235 pair.start
10236 .as_str()
10237 .chars()
10238 .count()
10239 .max(pair.end.as_str().chars().count())
10240 })
10241 .max();
10242 if let Some(max_lookup_length) = max_lookup_length {
10243 let next_text = snapshot
10244 .chars_at(selection_head)
10245 .take(max_lookup_length)
10246 .collect::<String>();
10247 let prev_text = snapshot
10248 .reversed_chars_at(selection_head)
10249 .take(max_lookup_length)
10250 .collect::<String>();
10251
10252 for (pair, enabled) in scope.brackets() {
10253 if enabled
10254 && pair.close
10255 && prev_text.starts_with(pair.start.as_str())
10256 && next_text.starts_with(pair.end.as_str())
10257 {
10258 bracket_pair = Some(pair.clone());
10259 break;
10260 }
10261 }
10262 }
10263
10264 if let Some(pair) = bracket_pair {
10265 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10266 let autoclose_enabled =
10267 self.use_autoclose && snapshot_settings.use_autoclose;
10268 if autoclose_enabled {
10269 let start = snapshot.anchor_after(selection_head);
10270 let end = snapshot.anchor_after(selection_head);
10271 self.autoclose_regions.push(AutocloseRegion {
10272 selection_id: selection.id,
10273 range: start..end,
10274 pair,
10275 });
10276 }
10277 }
10278 }
10279 }
10280 }
10281 Ok(())
10282 }
10283
10284 pub fn move_to_next_snippet_tabstop(
10285 &mut self,
10286 window: &mut Window,
10287 cx: &mut Context<Self>,
10288 ) -> bool {
10289 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10290 }
10291
10292 pub fn move_to_prev_snippet_tabstop(
10293 &mut self,
10294 window: &mut Window,
10295 cx: &mut Context<Self>,
10296 ) -> bool {
10297 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10298 }
10299
10300 pub fn move_to_snippet_tabstop(
10301 &mut self,
10302 bias: Bias,
10303 window: &mut Window,
10304 cx: &mut Context<Self>,
10305 ) -> bool {
10306 if let Some(mut snippet) = self.snippet_stack.pop() {
10307 match bias {
10308 Bias::Left => {
10309 if snippet.active_index > 0 {
10310 snippet.active_index -= 1;
10311 } else {
10312 self.snippet_stack.push(snippet);
10313 return false;
10314 }
10315 }
10316 Bias::Right => {
10317 if snippet.active_index + 1 < snippet.ranges.len() {
10318 snippet.active_index += 1;
10319 } else {
10320 self.snippet_stack.push(snippet);
10321 return false;
10322 }
10323 }
10324 }
10325 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10326 self.change_selections(Default::default(), window, cx, |s| {
10327 // Reverse order so that the first range is the newest created selection.
10328 // Completions will use it and autoscroll will prioritize it.
10329 s.select_ranges(current_ranges.iter().rev().cloned())
10330 });
10331
10332 if let Some(choices) = &snippet.choices[snippet.active_index]
10333 && let Some(selection) = current_ranges.first()
10334 {
10335 self.show_snippet_choices(choices, selection.clone(), cx);
10336 }
10337
10338 // If snippet state is not at the last tabstop, push it back on the stack
10339 if snippet.active_index + 1 < snippet.ranges.len() {
10340 self.snippet_stack.push(snippet);
10341 }
10342 return true;
10343 }
10344 }
10345
10346 false
10347 }
10348
10349 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10350 self.transact(window, cx, |this, window, cx| {
10351 this.select_all(&SelectAll, window, cx);
10352 this.insert("", window, cx);
10353 });
10354 }
10355
10356 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10357 if self.read_only(cx) {
10358 return;
10359 }
10360 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10361 self.transact(window, cx, |this, window, cx| {
10362 this.select_autoclose_pair(window, cx);
10363
10364 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10365
10366 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10367 if !this.linked_edit_ranges.is_empty() {
10368 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10369 let snapshot = this.buffer.read(cx).snapshot(cx);
10370
10371 for selection in selections.iter() {
10372 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10373 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10374 if selection_start.buffer_id != selection_end.buffer_id {
10375 continue;
10376 }
10377 if let Some(ranges) =
10378 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10379 {
10380 for (buffer, entries) in ranges {
10381 linked_ranges.entry(buffer).or_default().extend(entries);
10382 }
10383 }
10384 }
10385 }
10386
10387 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10388 for selection in &mut selections {
10389 if selection.is_empty() {
10390 let old_head = selection.head();
10391 let mut new_head =
10392 movement::left(&display_map, old_head.to_display_point(&display_map))
10393 .to_point(&display_map);
10394 if let Some((buffer, line_buffer_range)) = display_map
10395 .buffer_snapshot()
10396 .buffer_line_for_row(MultiBufferRow(old_head.row))
10397 {
10398 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10399 let indent_len = match indent_size.kind {
10400 IndentKind::Space => {
10401 buffer.settings_at(line_buffer_range.start, cx).tab_size
10402 }
10403 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10404 };
10405 if old_head.column <= indent_size.len && old_head.column > 0 {
10406 let indent_len = indent_len.get();
10407 new_head = cmp::min(
10408 new_head,
10409 MultiBufferPoint::new(
10410 old_head.row,
10411 ((old_head.column - 1) / indent_len) * indent_len,
10412 ),
10413 );
10414 }
10415 }
10416
10417 selection.set_head(new_head, SelectionGoal::None);
10418 }
10419 }
10420
10421 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10422 this.insert("", window, cx);
10423 let empty_str: Arc<str> = Arc::from("");
10424 for (buffer, edits) in linked_ranges {
10425 let snapshot = buffer.read(cx).snapshot();
10426 use text::ToPoint as TP;
10427
10428 let edits = edits
10429 .into_iter()
10430 .map(|range| {
10431 let end_point = TP::to_point(&range.end, &snapshot);
10432 let mut start_point = TP::to_point(&range.start, &snapshot);
10433
10434 if end_point == start_point {
10435 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10436 .saturating_sub(1);
10437 start_point =
10438 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10439 };
10440
10441 (start_point..end_point, empty_str.clone())
10442 })
10443 .sorted_by_key(|(range, _)| range.start)
10444 .collect::<Vec<_>>();
10445 buffer.update(cx, |this, cx| {
10446 this.edit(edits, None, cx);
10447 })
10448 }
10449 this.refresh_edit_prediction(true, false, window, cx);
10450 refresh_linked_ranges(this, window, cx);
10451 });
10452 }
10453
10454 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10455 if self.read_only(cx) {
10456 return;
10457 }
10458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10459 self.transact(window, cx, |this, window, cx| {
10460 this.change_selections(Default::default(), window, cx, |s| {
10461 s.move_with(|map, selection| {
10462 if selection.is_empty() {
10463 let cursor = movement::right(map, selection.head());
10464 selection.end = cursor;
10465 selection.reversed = true;
10466 selection.goal = SelectionGoal::None;
10467 }
10468 })
10469 });
10470 this.insert("", window, cx);
10471 this.refresh_edit_prediction(true, false, window, cx);
10472 });
10473 }
10474
10475 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10476 if self.mode.is_single_line() {
10477 cx.propagate();
10478 return;
10479 }
10480
10481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10482 if self.move_to_prev_snippet_tabstop(window, cx) {
10483 return;
10484 }
10485 self.outdent(&Outdent, window, cx);
10486 }
10487
10488 pub fn next_snippet_tabstop(
10489 &mut self,
10490 _: &NextSnippetTabstop,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10495 cx.propagate();
10496 return;
10497 }
10498
10499 if self.move_to_next_snippet_tabstop(window, cx) {
10500 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10501 return;
10502 }
10503 cx.propagate();
10504 }
10505
10506 pub fn previous_snippet_tabstop(
10507 &mut self,
10508 _: &PreviousSnippetTabstop,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10513 cx.propagate();
10514 return;
10515 }
10516
10517 if self.move_to_prev_snippet_tabstop(window, cx) {
10518 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10519 return;
10520 }
10521 cx.propagate();
10522 }
10523
10524 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10525 if self.mode.is_single_line() {
10526 cx.propagate();
10527 return;
10528 }
10529
10530 if self.move_to_next_snippet_tabstop(window, cx) {
10531 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10532 return;
10533 }
10534 if self.read_only(cx) {
10535 return;
10536 }
10537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10538 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10539 let buffer = self.buffer.read(cx);
10540 let snapshot = buffer.snapshot(cx);
10541 let rows_iter = selections.iter().map(|s| s.head().row);
10542 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10543
10544 let has_some_cursor_in_whitespace = selections
10545 .iter()
10546 .filter(|selection| selection.is_empty())
10547 .any(|selection| {
10548 let cursor = selection.head();
10549 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10550 cursor.column < current_indent.len
10551 });
10552
10553 let mut edits = Vec::new();
10554 let mut prev_edited_row = 0;
10555 let mut row_delta = 0;
10556 for selection in &mut selections {
10557 if selection.start.row != prev_edited_row {
10558 row_delta = 0;
10559 }
10560 prev_edited_row = selection.end.row;
10561
10562 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10563 if selection.is_empty() {
10564 let cursor = selection.head();
10565 let settings = buffer.language_settings_at(cursor, cx);
10566 if settings.indent_list_on_tab {
10567 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10568 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10569 row_delta = Self::indent_selection(
10570 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10571 );
10572 continue;
10573 }
10574 }
10575 }
10576 }
10577
10578 // If the selection is non-empty, then increase the indentation of the selected lines.
10579 if !selection.is_empty() {
10580 row_delta =
10581 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10582 continue;
10583 }
10584
10585 let cursor = selection.head();
10586 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10587 if let Some(suggested_indent) =
10588 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10589 {
10590 // Don't do anything if already at suggested indent
10591 // and there is any other cursor which is not
10592 if has_some_cursor_in_whitespace
10593 && cursor.column == current_indent.len
10594 && current_indent.len == suggested_indent.len
10595 {
10596 continue;
10597 }
10598
10599 // Adjust line and move cursor to suggested indent
10600 // if cursor is not at suggested indent
10601 if cursor.column < suggested_indent.len
10602 && cursor.column <= current_indent.len
10603 && current_indent.len <= suggested_indent.len
10604 {
10605 selection.start = Point::new(cursor.row, suggested_indent.len);
10606 selection.end = selection.start;
10607 if row_delta == 0 {
10608 edits.extend(Buffer::edit_for_indent_size_adjustment(
10609 cursor.row,
10610 current_indent,
10611 suggested_indent,
10612 ));
10613 row_delta = suggested_indent.len - current_indent.len;
10614 }
10615 continue;
10616 }
10617
10618 // If current indent is more than suggested indent
10619 // only move cursor to current indent and skip indent
10620 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10621 selection.start = Point::new(cursor.row, current_indent.len);
10622 selection.end = selection.start;
10623 continue;
10624 }
10625 }
10626
10627 // Otherwise, insert a hard or soft tab.
10628 let settings = buffer.language_settings_at(cursor, cx);
10629 let tab_size = if settings.hard_tabs {
10630 IndentSize::tab()
10631 } else {
10632 let tab_size = settings.tab_size.get();
10633 let indent_remainder = snapshot
10634 .text_for_range(Point::new(cursor.row, 0)..cursor)
10635 .flat_map(str::chars)
10636 .fold(row_delta % tab_size, |counter: u32, c| {
10637 if c == '\t' {
10638 0
10639 } else {
10640 (counter + 1) % tab_size
10641 }
10642 });
10643
10644 let chars_to_next_tab_stop = tab_size - indent_remainder;
10645 IndentSize::spaces(chars_to_next_tab_stop)
10646 };
10647 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10648 selection.end = selection.start;
10649 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10650 row_delta += tab_size.len;
10651 }
10652
10653 self.transact(window, cx, |this, window, cx| {
10654 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10655 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10656 this.refresh_edit_prediction(true, false, window, cx);
10657 });
10658 }
10659
10660 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10661 if self.read_only(cx) {
10662 return;
10663 }
10664 if self.mode.is_single_line() {
10665 cx.propagate();
10666 return;
10667 }
10668
10669 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10670 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10671 let mut prev_edited_row = 0;
10672 let mut row_delta = 0;
10673 let mut edits = Vec::new();
10674 let buffer = self.buffer.read(cx);
10675 let snapshot = buffer.snapshot(cx);
10676 for selection in &mut selections {
10677 if selection.start.row != prev_edited_row {
10678 row_delta = 0;
10679 }
10680 prev_edited_row = selection.end.row;
10681
10682 row_delta =
10683 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10684 }
10685
10686 self.transact(window, cx, |this, window, cx| {
10687 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10688 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10689 });
10690 }
10691
10692 fn indent_selection(
10693 buffer: &MultiBuffer,
10694 snapshot: &MultiBufferSnapshot,
10695 selection: &mut Selection<Point>,
10696 edits: &mut Vec<(Range<Point>, String)>,
10697 delta_for_start_row: u32,
10698 cx: &App,
10699 ) -> u32 {
10700 let settings = buffer.language_settings_at(selection.start, cx);
10701 let tab_size = settings.tab_size.get();
10702 let indent_kind = if settings.hard_tabs {
10703 IndentKind::Tab
10704 } else {
10705 IndentKind::Space
10706 };
10707 let mut start_row = selection.start.row;
10708 let mut end_row = selection.end.row + 1;
10709
10710 // If a selection ends at the beginning of a line, don't indent
10711 // that last line.
10712 if selection.end.column == 0 && selection.end.row > selection.start.row {
10713 end_row -= 1;
10714 }
10715
10716 // Avoid re-indenting a row that has already been indented by a
10717 // previous selection, but still update this selection's column
10718 // to reflect that indentation.
10719 if delta_for_start_row > 0 {
10720 start_row += 1;
10721 selection.start.column += delta_for_start_row;
10722 if selection.end.row == selection.start.row {
10723 selection.end.column += delta_for_start_row;
10724 }
10725 }
10726
10727 let mut delta_for_end_row = 0;
10728 let has_multiple_rows = start_row + 1 != end_row;
10729 for row in start_row..end_row {
10730 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10731 let indent_delta = match (current_indent.kind, indent_kind) {
10732 (IndentKind::Space, IndentKind::Space) => {
10733 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10734 IndentSize::spaces(columns_to_next_tab_stop)
10735 }
10736 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10737 (_, IndentKind::Tab) => IndentSize::tab(),
10738 };
10739
10740 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10741 0
10742 } else {
10743 selection.start.column
10744 };
10745 let row_start = Point::new(row, start);
10746 edits.push((
10747 row_start..row_start,
10748 indent_delta.chars().collect::<String>(),
10749 ));
10750
10751 // Update this selection's endpoints to reflect the indentation.
10752 if row == selection.start.row {
10753 selection.start.column += indent_delta.len;
10754 }
10755 if row == selection.end.row {
10756 selection.end.column += indent_delta.len;
10757 delta_for_end_row = indent_delta.len;
10758 }
10759 }
10760
10761 if selection.start.row == selection.end.row {
10762 delta_for_start_row + delta_for_end_row
10763 } else {
10764 delta_for_end_row
10765 }
10766 }
10767
10768 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10769 if self.read_only(cx) {
10770 return;
10771 }
10772 if self.mode.is_single_line() {
10773 cx.propagate();
10774 return;
10775 }
10776
10777 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10779 let selections = self.selections.all::<Point>(&display_map);
10780 let mut deletion_ranges = Vec::new();
10781 let mut last_outdent = None;
10782 {
10783 let buffer = self.buffer.read(cx);
10784 let snapshot = buffer.snapshot(cx);
10785 for selection in &selections {
10786 let settings = buffer.language_settings_at(selection.start, cx);
10787 let tab_size = settings.tab_size.get();
10788 let mut rows = selection.spanned_rows(false, &display_map);
10789
10790 // Avoid re-outdenting a row that has already been outdented by a
10791 // previous selection.
10792 if let Some(last_row) = last_outdent
10793 && last_row == rows.start
10794 {
10795 rows.start = rows.start.next_row();
10796 }
10797 let has_multiple_rows = rows.len() > 1;
10798 for row in rows.iter_rows() {
10799 let indent_size = snapshot.indent_size_for_line(row);
10800 if indent_size.len > 0 {
10801 let deletion_len = match indent_size.kind {
10802 IndentKind::Space => {
10803 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10804 if columns_to_prev_tab_stop == 0 {
10805 tab_size
10806 } else {
10807 columns_to_prev_tab_stop
10808 }
10809 }
10810 IndentKind::Tab => 1,
10811 };
10812 let start = if has_multiple_rows
10813 || deletion_len > selection.start.column
10814 || indent_size.len < selection.start.column
10815 {
10816 0
10817 } else {
10818 selection.start.column - deletion_len
10819 };
10820 deletion_ranges.push(
10821 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10822 );
10823 last_outdent = Some(row);
10824 }
10825 }
10826 }
10827 }
10828
10829 self.transact(window, cx, |this, window, cx| {
10830 this.buffer.update(cx, |buffer, cx| {
10831 let empty_str: Arc<str> = Arc::default();
10832 buffer.edit(
10833 deletion_ranges
10834 .into_iter()
10835 .map(|range| (range, empty_str.clone())),
10836 None,
10837 cx,
10838 );
10839 });
10840 let selections = this
10841 .selections
10842 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10843 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10844 });
10845 }
10846
10847 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10848 if self.read_only(cx) {
10849 return;
10850 }
10851 if self.mode.is_single_line() {
10852 cx.propagate();
10853 return;
10854 }
10855
10856 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10857 let selections = self
10858 .selections
10859 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10860 .into_iter()
10861 .map(|s| s.range());
10862
10863 self.transact(window, cx, |this, window, cx| {
10864 this.buffer.update(cx, |buffer, cx| {
10865 buffer.autoindent_ranges(selections, cx);
10866 });
10867 let selections = this
10868 .selections
10869 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10870 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10871 });
10872 }
10873
10874 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10875 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10876 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10877 let selections = self.selections.all::<Point>(&display_map);
10878
10879 let mut new_cursors = Vec::new();
10880 let mut edit_ranges = Vec::new();
10881 let mut selections = selections.iter().peekable();
10882 while let Some(selection) = selections.next() {
10883 let mut rows = selection.spanned_rows(false, &display_map);
10884
10885 // Accumulate contiguous regions of rows that we want to delete.
10886 while let Some(next_selection) = selections.peek() {
10887 let next_rows = next_selection.spanned_rows(false, &display_map);
10888 if next_rows.start <= rows.end {
10889 rows.end = next_rows.end;
10890 selections.next().unwrap();
10891 } else {
10892 break;
10893 }
10894 }
10895
10896 let buffer = display_map.buffer_snapshot();
10897 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10898 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10899 // If there's a line after the range, delete the \n from the end of the row range
10900 (
10901 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10902 rows.end,
10903 )
10904 } else {
10905 // If there isn't a line after the range, delete the \n from the line before the
10906 // start of the row range
10907 edit_start = edit_start.saturating_sub_usize(1);
10908 (buffer.len(), rows.start.previous_row())
10909 };
10910
10911 let text_layout_details = self.text_layout_details(window);
10912 let x = display_map.x_for_display_point(
10913 selection.head().to_display_point(&display_map),
10914 &text_layout_details,
10915 );
10916 let row = Point::new(target_row.0, 0)
10917 .to_display_point(&display_map)
10918 .row();
10919 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10920
10921 new_cursors.push((
10922 selection.id,
10923 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10924 SelectionGoal::None,
10925 ));
10926 edit_ranges.push(edit_start..edit_end);
10927 }
10928
10929 self.transact(window, cx, |this, window, cx| {
10930 let buffer = this.buffer.update(cx, |buffer, cx| {
10931 let empty_str: Arc<str> = Arc::default();
10932 buffer.edit(
10933 edit_ranges
10934 .into_iter()
10935 .map(|range| (range, empty_str.clone())),
10936 None,
10937 cx,
10938 );
10939 buffer.snapshot(cx)
10940 });
10941 let new_selections = new_cursors
10942 .into_iter()
10943 .map(|(id, cursor, goal)| {
10944 let cursor = cursor.to_point(&buffer);
10945 Selection {
10946 id,
10947 start: cursor,
10948 end: cursor,
10949 reversed: false,
10950 goal,
10951 }
10952 })
10953 .collect();
10954
10955 this.change_selections(Default::default(), window, cx, |s| {
10956 s.select(new_selections);
10957 });
10958 });
10959 }
10960
10961 pub fn join_lines_impl(
10962 &mut self,
10963 insert_whitespace: bool,
10964 window: &mut Window,
10965 cx: &mut Context<Self>,
10966 ) {
10967 if self.read_only(cx) {
10968 return;
10969 }
10970 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10971 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10972 let start = MultiBufferRow(selection.start.row);
10973 // Treat single line selections as if they include the next line. Otherwise this action
10974 // would do nothing for single line selections individual cursors.
10975 let end = if selection.start.row == selection.end.row {
10976 MultiBufferRow(selection.start.row + 1)
10977 } else {
10978 MultiBufferRow(selection.end.row)
10979 };
10980
10981 if let Some(last_row_range) = row_ranges.last_mut()
10982 && start <= last_row_range.end
10983 {
10984 last_row_range.end = end;
10985 continue;
10986 }
10987 row_ranges.push(start..end);
10988 }
10989
10990 let snapshot = self.buffer.read(cx).snapshot(cx);
10991 let mut cursor_positions = Vec::new();
10992 for row_range in &row_ranges {
10993 let anchor = snapshot.anchor_before(Point::new(
10994 row_range.end.previous_row().0,
10995 snapshot.line_len(row_range.end.previous_row()),
10996 ));
10997 cursor_positions.push(anchor..anchor);
10998 }
10999
11000 self.transact(window, cx, |this, window, cx| {
11001 for row_range in row_ranges.into_iter().rev() {
11002 for row in row_range.iter_rows().rev() {
11003 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11004 let next_line_row = row.next_row();
11005 let indent = snapshot.indent_size_for_line(next_line_row);
11006 let start_of_next_line = Point::new(next_line_row.0, indent.len);
11007
11008 let replace =
11009 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
11010 " "
11011 } else {
11012 ""
11013 };
11014
11015 this.buffer.update(cx, |buffer, cx| {
11016 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11017 });
11018 }
11019 }
11020
11021 this.change_selections(Default::default(), window, cx, |s| {
11022 s.select_anchor_ranges(cursor_positions)
11023 });
11024 });
11025 }
11026
11027 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11029 self.join_lines_impl(true, window, cx);
11030 }
11031
11032 pub fn sort_lines_case_sensitive(
11033 &mut self,
11034 _: &SortLinesCaseSensitive,
11035 window: &mut Window,
11036 cx: &mut Context<Self>,
11037 ) {
11038 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11039 }
11040
11041 pub fn sort_lines_by_length(
11042 &mut self,
11043 _: &SortLinesByLength,
11044 window: &mut Window,
11045 cx: &mut Context<Self>,
11046 ) {
11047 self.manipulate_immutable_lines(window, cx, |lines| {
11048 lines.sort_by_key(|&line| line.chars().count())
11049 })
11050 }
11051
11052 pub fn sort_lines_case_insensitive(
11053 &mut self,
11054 _: &SortLinesCaseInsensitive,
11055 window: &mut Window,
11056 cx: &mut Context<Self>,
11057 ) {
11058 self.manipulate_immutable_lines(window, cx, |lines| {
11059 lines.sort_by_key(|line| line.to_lowercase())
11060 })
11061 }
11062
11063 pub fn unique_lines_case_insensitive(
11064 &mut self,
11065 _: &UniqueLinesCaseInsensitive,
11066 window: &mut Window,
11067 cx: &mut Context<Self>,
11068 ) {
11069 self.manipulate_immutable_lines(window, cx, |lines| {
11070 let mut seen = HashSet::default();
11071 lines.retain(|line| seen.insert(line.to_lowercase()));
11072 })
11073 }
11074
11075 pub fn unique_lines_case_sensitive(
11076 &mut self,
11077 _: &UniqueLinesCaseSensitive,
11078 window: &mut Window,
11079 cx: &mut Context<Self>,
11080 ) {
11081 self.manipulate_immutable_lines(window, cx, |lines| {
11082 let mut seen = HashSet::default();
11083 lines.retain(|line| seen.insert(*line));
11084 })
11085 }
11086
11087 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11088 let snapshot = self.buffer.read(cx).snapshot(cx);
11089 for selection in self.selections.disjoint_anchors_arc().iter() {
11090 if snapshot
11091 .language_at(selection.start)
11092 .and_then(|lang| lang.config().wrap_characters.as_ref())
11093 .is_some()
11094 {
11095 return true;
11096 }
11097 }
11098 false
11099 }
11100
11101 fn wrap_selections_in_tag(
11102 &mut self,
11103 _: &WrapSelectionsInTag,
11104 window: &mut Window,
11105 cx: &mut Context<Self>,
11106 ) {
11107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11108
11109 let snapshot = self.buffer.read(cx).snapshot(cx);
11110
11111 let mut edits = Vec::new();
11112 let mut boundaries = Vec::new();
11113
11114 for selection in self
11115 .selections
11116 .all_adjusted(&self.display_snapshot(cx))
11117 .iter()
11118 {
11119 let Some(wrap_config) = snapshot
11120 .language_at(selection.start)
11121 .and_then(|lang| lang.config().wrap_characters.clone())
11122 else {
11123 continue;
11124 };
11125
11126 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11127 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11128
11129 let start_before = snapshot.anchor_before(selection.start);
11130 let end_after = snapshot.anchor_after(selection.end);
11131
11132 edits.push((start_before..start_before, open_tag));
11133 edits.push((end_after..end_after, close_tag));
11134
11135 boundaries.push((
11136 start_before,
11137 end_after,
11138 wrap_config.start_prefix.len(),
11139 wrap_config.end_suffix.len(),
11140 ));
11141 }
11142
11143 if edits.is_empty() {
11144 return;
11145 }
11146
11147 self.transact(window, cx, |this, window, cx| {
11148 let buffer = this.buffer.update(cx, |buffer, cx| {
11149 buffer.edit(edits, None, cx);
11150 buffer.snapshot(cx)
11151 });
11152
11153 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11154 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11155 boundaries.into_iter()
11156 {
11157 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11158 let close_offset = end_after
11159 .to_offset(&buffer)
11160 .saturating_sub_usize(end_suffix_len);
11161 new_selections.push(open_offset..open_offset);
11162 new_selections.push(close_offset..close_offset);
11163 }
11164
11165 this.change_selections(Default::default(), window, cx, |s| {
11166 s.select_ranges(new_selections);
11167 });
11168
11169 this.request_autoscroll(Autoscroll::fit(), cx);
11170 });
11171 }
11172
11173 pub fn toggle_read_only(
11174 &mut self,
11175 _: &workspace::ToggleReadOnlyFile,
11176 _: &mut Window,
11177 cx: &mut Context<Self>,
11178 ) {
11179 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11180 buffer.update(cx, |buffer, cx| {
11181 buffer.set_capability(
11182 match buffer.capability() {
11183 Capability::ReadWrite => Capability::Read,
11184 Capability::Read => Capability::ReadWrite,
11185 Capability::ReadOnly => Capability::ReadOnly,
11186 },
11187 cx,
11188 );
11189 })
11190 }
11191 }
11192
11193 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11194 let Some(project) = self.project.clone() else {
11195 return;
11196 };
11197 self.reload(project, window, cx)
11198 .detach_and_notify_err(window, cx);
11199 }
11200
11201 pub fn restore_file(
11202 &mut self,
11203 _: &::git::RestoreFile,
11204 window: &mut Window,
11205 cx: &mut Context<Self>,
11206 ) {
11207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11208 let mut buffer_ids = HashSet::default();
11209 let snapshot = self.buffer().read(cx).snapshot(cx);
11210 for selection in self
11211 .selections
11212 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11213 {
11214 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11215 }
11216
11217 let buffer = self.buffer().read(cx);
11218 let ranges = buffer_ids
11219 .into_iter()
11220 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11221 .collect::<Vec<_>>();
11222
11223 self.restore_hunks_in_ranges(ranges, window, cx);
11224 }
11225
11226 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11227 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11228 let selections = self
11229 .selections
11230 .all(&self.display_snapshot(cx))
11231 .into_iter()
11232 .map(|s| s.range())
11233 .collect();
11234 self.restore_hunks_in_ranges(selections, window, cx);
11235 }
11236
11237 pub fn restore_hunks_in_ranges(
11238 &mut self,
11239 ranges: Vec<Range<Point>>,
11240 window: &mut Window,
11241 cx: &mut Context<Editor>,
11242 ) {
11243 let mut revert_changes = HashMap::default();
11244 let chunk_by = self
11245 .snapshot(window, cx)
11246 .hunks_for_ranges(ranges)
11247 .into_iter()
11248 .chunk_by(|hunk| hunk.buffer_id);
11249 for (buffer_id, hunks) in &chunk_by {
11250 let hunks = hunks.collect::<Vec<_>>();
11251 for hunk in &hunks {
11252 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11253 }
11254 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11255 }
11256 drop(chunk_by);
11257 if !revert_changes.is_empty() {
11258 self.transact(window, cx, |editor, window, cx| {
11259 editor.restore(revert_changes, window, cx);
11260 });
11261 }
11262 }
11263
11264 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11265 if let Some(status) = self
11266 .addons
11267 .iter()
11268 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11269 {
11270 return Some(status);
11271 }
11272 self.project
11273 .as_ref()?
11274 .read(cx)
11275 .status_for_buffer_id(buffer_id, cx)
11276 }
11277
11278 pub fn open_active_item_in_terminal(
11279 &mut self,
11280 _: &OpenInTerminal,
11281 window: &mut Window,
11282 cx: &mut Context<Self>,
11283 ) {
11284 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11285 let project_path = buffer.read(cx).project_path(cx)?;
11286 let project = self.project()?.read(cx);
11287 let entry = project.entry_for_path(&project_path, cx)?;
11288 let parent = match &entry.canonical_path {
11289 Some(canonical_path) => canonical_path.to_path_buf(),
11290 None => project.absolute_path(&project_path, cx)?,
11291 }
11292 .parent()?
11293 .to_path_buf();
11294 Some(parent)
11295 }) {
11296 window.dispatch_action(
11297 OpenTerminal {
11298 working_directory,
11299 local: false,
11300 }
11301 .boxed_clone(),
11302 cx,
11303 );
11304 }
11305 }
11306
11307 fn set_breakpoint_context_menu(
11308 &mut self,
11309 display_row: DisplayRow,
11310 position: Option<Anchor>,
11311 clicked_point: gpui::Point<Pixels>,
11312 window: &mut Window,
11313 cx: &mut Context<Self>,
11314 ) {
11315 let source = self
11316 .buffer
11317 .read(cx)
11318 .snapshot(cx)
11319 .anchor_before(Point::new(display_row.0, 0u32));
11320
11321 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11322
11323 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11324 self,
11325 source,
11326 clicked_point,
11327 context_menu,
11328 window,
11329 cx,
11330 );
11331 }
11332
11333 fn add_edit_breakpoint_block(
11334 &mut self,
11335 anchor: Anchor,
11336 breakpoint: &Breakpoint,
11337 edit_action: BreakpointPromptEditAction,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 let weak_editor = cx.weak_entity();
11342 let bp_prompt = cx.new(|cx| {
11343 BreakpointPromptEditor::new(
11344 weak_editor,
11345 anchor,
11346 breakpoint.clone(),
11347 edit_action,
11348 window,
11349 cx,
11350 )
11351 });
11352
11353 let height = bp_prompt.update(cx, |this, cx| {
11354 this.prompt
11355 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11356 });
11357 let cloned_prompt = bp_prompt.clone();
11358 let blocks = vec![BlockProperties {
11359 style: BlockStyle::Sticky,
11360 placement: BlockPlacement::Above(anchor),
11361 height: Some(height),
11362 render: Arc::new(move |cx| {
11363 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11364 cloned_prompt.clone().into_any_element()
11365 }),
11366 priority: 0,
11367 }];
11368
11369 let focus_handle = bp_prompt.focus_handle(cx);
11370 window.focus(&focus_handle, cx);
11371
11372 let block_ids = self.insert_blocks(blocks, None, cx);
11373 bp_prompt.update(cx, |prompt, _| {
11374 prompt.add_block_ids(block_ids);
11375 });
11376 }
11377
11378 pub(crate) fn breakpoint_at_row(
11379 &self,
11380 row: u32,
11381 window: &mut Window,
11382 cx: &mut Context<Self>,
11383 ) -> Option<(Anchor, Breakpoint)> {
11384 let snapshot = self.snapshot(window, cx);
11385 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11386
11387 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11388 }
11389
11390 pub(crate) fn breakpoint_at_anchor(
11391 &self,
11392 breakpoint_position: Anchor,
11393 snapshot: &EditorSnapshot,
11394 cx: &mut Context<Self>,
11395 ) -> Option<(Anchor, Breakpoint)> {
11396 let buffer = self
11397 .buffer
11398 .read(cx)
11399 .buffer_for_anchor(breakpoint_position, cx)?;
11400
11401 let enclosing_excerpt = breakpoint_position.excerpt_id;
11402 let buffer_snapshot = buffer.read(cx).snapshot();
11403
11404 let row = buffer_snapshot
11405 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11406 .row;
11407
11408 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11409 let anchor_end = snapshot
11410 .buffer_snapshot()
11411 .anchor_after(Point::new(row, line_len));
11412
11413 self.breakpoint_store
11414 .as_ref()?
11415 .read_with(cx, |breakpoint_store, cx| {
11416 breakpoint_store
11417 .breakpoints(
11418 &buffer,
11419 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11420 &buffer_snapshot,
11421 cx,
11422 )
11423 .next()
11424 .and_then(|(bp, _)| {
11425 let breakpoint_row = buffer_snapshot
11426 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11427 .row;
11428
11429 if breakpoint_row == row {
11430 snapshot
11431 .buffer_snapshot()
11432 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11433 .map(|position| (position, bp.bp.clone()))
11434 } else {
11435 None
11436 }
11437 })
11438 })
11439 }
11440
11441 pub fn edit_log_breakpoint(
11442 &mut self,
11443 _: &EditLogBreakpoint,
11444 window: &mut Window,
11445 cx: &mut Context<Self>,
11446 ) {
11447 if self.breakpoint_store.is_none() {
11448 return;
11449 }
11450
11451 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11452 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11453 message: None,
11454 state: BreakpointState::Enabled,
11455 condition: None,
11456 hit_condition: None,
11457 });
11458
11459 self.add_edit_breakpoint_block(
11460 anchor,
11461 &breakpoint,
11462 BreakpointPromptEditAction::Log,
11463 window,
11464 cx,
11465 );
11466 }
11467 }
11468
11469 fn breakpoints_at_cursors(
11470 &self,
11471 window: &mut Window,
11472 cx: &mut Context<Self>,
11473 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11474 let snapshot = self.snapshot(window, cx);
11475 let cursors = self
11476 .selections
11477 .disjoint_anchors_arc()
11478 .iter()
11479 .map(|selection| {
11480 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11481
11482 let breakpoint_position = self
11483 .breakpoint_at_row(cursor_position.row, window, cx)
11484 .map(|bp| bp.0)
11485 .unwrap_or_else(|| {
11486 snapshot
11487 .display_snapshot
11488 .buffer_snapshot()
11489 .anchor_after(Point::new(cursor_position.row, 0))
11490 });
11491
11492 let breakpoint = self
11493 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11494 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11495
11496 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11497 })
11498 // 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.
11499 .collect::<HashMap<Anchor, _>>();
11500
11501 cursors.into_iter().collect()
11502 }
11503
11504 pub fn enable_breakpoint(
11505 &mut self,
11506 _: &crate::actions::EnableBreakpoint,
11507 window: &mut Window,
11508 cx: &mut Context<Self>,
11509 ) {
11510 if self.breakpoint_store.is_none() {
11511 return;
11512 }
11513
11514 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11515 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11516 continue;
11517 };
11518 self.edit_breakpoint_at_anchor(
11519 anchor,
11520 breakpoint,
11521 BreakpointEditAction::InvertState,
11522 cx,
11523 );
11524 }
11525 }
11526
11527 pub fn disable_breakpoint(
11528 &mut self,
11529 _: &crate::actions::DisableBreakpoint,
11530 window: &mut Window,
11531 cx: &mut Context<Self>,
11532 ) {
11533 if self.breakpoint_store.is_none() {
11534 return;
11535 }
11536
11537 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11538 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11539 continue;
11540 };
11541 self.edit_breakpoint_at_anchor(
11542 anchor,
11543 breakpoint,
11544 BreakpointEditAction::InvertState,
11545 cx,
11546 );
11547 }
11548 }
11549
11550 pub fn toggle_breakpoint(
11551 &mut self,
11552 _: &crate::actions::ToggleBreakpoint,
11553 window: &mut Window,
11554 cx: &mut Context<Self>,
11555 ) {
11556 if self.breakpoint_store.is_none() {
11557 return;
11558 }
11559
11560 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11561 if let Some(breakpoint) = breakpoint {
11562 self.edit_breakpoint_at_anchor(
11563 anchor,
11564 breakpoint,
11565 BreakpointEditAction::Toggle,
11566 cx,
11567 );
11568 } else {
11569 self.edit_breakpoint_at_anchor(
11570 anchor,
11571 Breakpoint::new_standard(),
11572 BreakpointEditAction::Toggle,
11573 cx,
11574 );
11575 }
11576 }
11577 }
11578
11579 pub fn edit_breakpoint_at_anchor(
11580 &mut self,
11581 breakpoint_position: Anchor,
11582 breakpoint: Breakpoint,
11583 edit_action: BreakpointEditAction,
11584 cx: &mut Context<Self>,
11585 ) {
11586 let Some(breakpoint_store) = &self.breakpoint_store else {
11587 return;
11588 };
11589
11590 let Some(buffer) = self
11591 .buffer
11592 .read(cx)
11593 .buffer_for_anchor(breakpoint_position, cx)
11594 else {
11595 return;
11596 };
11597
11598 breakpoint_store.update(cx, |breakpoint_store, cx| {
11599 breakpoint_store.toggle_breakpoint(
11600 buffer,
11601 BreakpointWithPosition {
11602 position: breakpoint_position.text_anchor,
11603 bp: breakpoint,
11604 },
11605 edit_action,
11606 cx,
11607 );
11608 });
11609
11610 cx.notify();
11611 }
11612
11613 #[cfg(any(test, feature = "test-support"))]
11614 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11615 self.breakpoint_store.clone()
11616 }
11617
11618 pub fn prepare_restore_change(
11619 &self,
11620 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11621 hunk: &MultiBufferDiffHunk,
11622 cx: &mut App,
11623 ) -> Option<()> {
11624 if hunk.is_created_file() {
11625 return None;
11626 }
11627 let buffer = self.buffer.read(cx);
11628 let diff = buffer.diff_for(hunk.buffer_id)?;
11629 let buffer = buffer.buffer(hunk.buffer_id)?;
11630 let buffer = buffer.read(cx);
11631 let original_text = diff
11632 .read(cx)
11633 .base_text(cx)
11634 .as_rope()
11635 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11636 let buffer_snapshot = buffer.snapshot();
11637 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11638 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11639 probe
11640 .0
11641 .start
11642 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11643 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11644 }) {
11645 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11646 Some(())
11647 } else {
11648 None
11649 }
11650 }
11651
11652 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11653 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11654 }
11655
11656 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11657 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11658 }
11659
11660 pub fn rotate_selections_forward(
11661 &mut self,
11662 _: &RotateSelectionsForward,
11663 window: &mut Window,
11664 cx: &mut Context<Self>,
11665 ) {
11666 self.rotate_selections(window, cx, false)
11667 }
11668
11669 pub fn rotate_selections_backward(
11670 &mut self,
11671 _: &RotateSelectionsBackward,
11672 window: &mut Window,
11673 cx: &mut Context<Self>,
11674 ) {
11675 self.rotate_selections(window, cx, true)
11676 }
11677
11678 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11680 let display_snapshot = self.display_snapshot(cx);
11681 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11682
11683 if selections.len() < 2 {
11684 return;
11685 }
11686
11687 let (edits, new_selections) = {
11688 let buffer = self.buffer.read(cx).read(cx);
11689 let has_selections = selections.iter().any(|s| !s.is_empty());
11690 if has_selections {
11691 let mut selected_texts: Vec<String> = selections
11692 .iter()
11693 .map(|selection| {
11694 buffer
11695 .text_for_range(selection.start..selection.end)
11696 .collect()
11697 })
11698 .collect();
11699
11700 if reverse {
11701 selected_texts.rotate_left(1);
11702 } else {
11703 selected_texts.rotate_right(1);
11704 }
11705
11706 let mut offset_delta: i64 = 0;
11707 let mut new_selections = Vec::new();
11708 let edits: Vec<_> = selections
11709 .iter()
11710 .zip(selected_texts.iter())
11711 .map(|(selection, new_text)| {
11712 let old_len = (selection.end.0 - selection.start.0) as i64;
11713 let new_len = new_text.len() as i64;
11714 let adjusted_start =
11715 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11716 let adjusted_end =
11717 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11718
11719 new_selections.push(Selection {
11720 id: selection.id,
11721 start: adjusted_start,
11722 end: adjusted_end,
11723 reversed: selection.reversed,
11724 goal: selection.goal,
11725 });
11726
11727 offset_delta += new_len - old_len;
11728 (selection.start..selection.end, new_text.clone())
11729 })
11730 .collect();
11731 (edits, new_selections)
11732 } else {
11733 let mut all_rows: Vec<u32> = selections
11734 .iter()
11735 .map(|selection| buffer.offset_to_point(selection.start).row)
11736 .collect();
11737 all_rows.sort_unstable();
11738 all_rows.dedup();
11739
11740 if all_rows.len() < 2 {
11741 return;
11742 }
11743
11744 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11745 .iter()
11746 .map(|&row| {
11747 let start = Point::new(row, 0);
11748 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11749 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11750 })
11751 .collect();
11752
11753 let mut line_texts: Vec<String> = line_ranges
11754 .iter()
11755 .map(|range| buffer.text_for_range(range.clone()).collect())
11756 .collect();
11757
11758 if reverse {
11759 line_texts.rotate_left(1);
11760 } else {
11761 line_texts.rotate_right(1);
11762 }
11763
11764 let edits = line_ranges
11765 .iter()
11766 .zip(line_texts.iter())
11767 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11768 .collect();
11769
11770 let num_rows = all_rows.len();
11771 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11772 .iter()
11773 .enumerate()
11774 .map(|(i, &row)| (row, i))
11775 .collect();
11776
11777 // Compute new line start offsets after rotation (handles CRLF)
11778 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11779 let first_line_start = line_ranges[0].start.0;
11780 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11781 for text in line_texts.iter().take(num_rows - 1) {
11782 let prev_start = *new_line_starts.last().unwrap();
11783 new_line_starts.push(prev_start + text.len() + newline_len);
11784 }
11785
11786 let new_selections = selections
11787 .iter()
11788 .map(|selection| {
11789 let point = buffer.offset_to_point(selection.start);
11790 let old_index = row_to_index[&point.row];
11791 let new_index = if reverse {
11792 (old_index + num_rows - 1) % num_rows
11793 } else {
11794 (old_index + 1) % num_rows
11795 };
11796 let new_offset =
11797 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11798 Selection {
11799 id: selection.id,
11800 start: new_offset,
11801 end: new_offset,
11802 reversed: selection.reversed,
11803 goal: selection.goal,
11804 }
11805 })
11806 .collect();
11807
11808 (edits, new_selections)
11809 }
11810 };
11811
11812 self.transact(window, cx, |this, window, cx| {
11813 this.buffer.update(cx, |buffer, cx| {
11814 buffer.edit(edits, None, cx);
11815 });
11816 this.change_selections(Default::default(), window, cx, |s| {
11817 s.select(new_selections);
11818 });
11819 });
11820 }
11821
11822 fn manipulate_lines<M>(
11823 &mut self,
11824 window: &mut Window,
11825 cx: &mut Context<Self>,
11826 mut manipulate: M,
11827 ) where
11828 M: FnMut(&str) -> LineManipulationResult,
11829 {
11830 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11831
11832 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11833 let buffer = self.buffer.read(cx).snapshot(cx);
11834
11835 let mut edits = Vec::new();
11836
11837 let selections = self.selections.all::<Point>(&display_map);
11838 let mut selections = selections.iter().peekable();
11839 let mut contiguous_row_selections = Vec::new();
11840 let mut new_selections = Vec::new();
11841 let mut added_lines = 0;
11842 let mut removed_lines = 0;
11843
11844 while let Some(selection) = selections.next() {
11845 let (start_row, end_row) = consume_contiguous_rows(
11846 &mut contiguous_row_selections,
11847 selection,
11848 &display_map,
11849 &mut selections,
11850 );
11851
11852 let start_point = Point::new(start_row.0, 0);
11853 let end_point = Point::new(
11854 end_row.previous_row().0,
11855 buffer.line_len(end_row.previous_row()),
11856 );
11857 let text = buffer
11858 .text_for_range(start_point..end_point)
11859 .collect::<String>();
11860
11861 let LineManipulationResult {
11862 new_text,
11863 line_count_before,
11864 line_count_after,
11865 } = manipulate(&text);
11866
11867 edits.push((start_point..end_point, new_text));
11868
11869 // Selections must change based on added and removed line count
11870 let start_row =
11871 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11872 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11873 new_selections.push(Selection {
11874 id: selection.id,
11875 start: start_row,
11876 end: end_row,
11877 goal: SelectionGoal::None,
11878 reversed: selection.reversed,
11879 });
11880
11881 if line_count_after > line_count_before {
11882 added_lines += line_count_after - line_count_before;
11883 } else if line_count_before > line_count_after {
11884 removed_lines += line_count_before - line_count_after;
11885 }
11886 }
11887
11888 self.transact(window, cx, |this, window, cx| {
11889 let buffer = this.buffer.update(cx, |buffer, cx| {
11890 buffer.edit(edits, None, cx);
11891 buffer.snapshot(cx)
11892 });
11893
11894 // Recalculate offsets on newly edited buffer
11895 let new_selections = new_selections
11896 .iter()
11897 .map(|s| {
11898 let start_point = Point::new(s.start.0, 0);
11899 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11900 Selection {
11901 id: s.id,
11902 start: buffer.point_to_offset(start_point),
11903 end: buffer.point_to_offset(end_point),
11904 goal: s.goal,
11905 reversed: s.reversed,
11906 }
11907 })
11908 .collect();
11909
11910 this.change_selections(Default::default(), window, cx, |s| {
11911 s.select(new_selections);
11912 });
11913
11914 this.request_autoscroll(Autoscroll::fit(), cx);
11915 });
11916 }
11917
11918 fn manipulate_immutable_lines<Fn>(
11919 &mut self,
11920 window: &mut Window,
11921 cx: &mut Context<Self>,
11922 mut callback: Fn,
11923 ) where
11924 Fn: FnMut(&mut Vec<&str>),
11925 {
11926 self.manipulate_lines(window, cx, |text| {
11927 let mut lines: Vec<&str> = text.split('\n').collect();
11928 let line_count_before = lines.len();
11929
11930 callback(&mut lines);
11931
11932 LineManipulationResult {
11933 new_text: lines.join("\n"),
11934 line_count_before,
11935 line_count_after: lines.len(),
11936 }
11937 });
11938 }
11939
11940 fn manipulate_mutable_lines<Fn>(
11941 &mut self,
11942 window: &mut Window,
11943 cx: &mut Context<Self>,
11944 mut callback: Fn,
11945 ) where
11946 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11947 {
11948 self.manipulate_lines(window, cx, |text| {
11949 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11950 let line_count_before = lines.len();
11951
11952 callback(&mut lines);
11953
11954 LineManipulationResult {
11955 new_text: lines.join("\n"),
11956 line_count_before,
11957 line_count_after: lines.len(),
11958 }
11959 });
11960 }
11961
11962 pub fn convert_indentation_to_spaces(
11963 &mut self,
11964 _: &ConvertIndentationToSpaces,
11965 window: &mut Window,
11966 cx: &mut Context<Self>,
11967 ) {
11968 let settings = self.buffer.read(cx).language_settings(cx);
11969 let tab_size = settings.tab_size.get() as usize;
11970
11971 self.manipulate_mutable_lines(window, cx, |lines| {
11972 // Allocates a reasonably sized scratch buffer once for the whole loop
11973 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11974 // Avoids recomputing spaces that could be inserted many times
11975 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11976 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11977 .collect();
11978
11979 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11980 let mut chars = line.as_ref().chars();
11981 let mut col = 0;
11982 let mut changed = false;
11983
11984 for ch in chars.by_ref() {
11985 match ch {
11986 ' ' => {
11987 reindented_line.push(' ');
11988 col += 1;
11989 }
11990 '\t' => {
11991 // \t are converted to spaces depending on the current column
11992 let spaces_len = tab_size - (col % tab_size);
11993 reindented_line.extend(&space_cache[spaces_len - 1]);
11994 col += spaces_len;
11995 changed = true;
11996 }
11997 _ => {
11998 // If we dont append before break, the character is consumed
11999 reindented_line.push(ch);
12000 break;
12001 }
12002 }
12003 }
12004
12005 if !changed {
12006 reindented_line.clear();
12007 continue;
12008 }
12009 // Append the rest of the line and replace old reference with new one
12010 reindented_line.extend(chars);
12011 *line = Cow::Owned(reindented_line.clone());
12012 reindented_line.clear();
12013 }
12014 });
12015 }
12016
12017 pub fn convert_indentation_to_tabs(
12018 &mut self,
12019 _: &ConvertIndentationToTabs,
12020 window: &mut Window,
12021 cx: &mut Context<Self>,
12022 ) {
12023 let settings = self.buffer.read(cx).language_settings(cx);
12024 let tab_size = settings.tab_size.get() as usize;
12025
12026 self.manipulate_mutable_lines(window, cx, |lines| {
12027 // Allocates a reasonably sized buffer once for the whole loop
12028 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12029 // Avoids recomputing spaces that could be inserted many times
12030 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12031 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12032 .collect();
12033
12034 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12035 let mut chars = line.chars();
12036 let mut spaces_count = 0;
12037 let mut first_non_indent_char = None;
12038 let mut changed = false;
12039
12040 for ch in chars.by_ref() {
12041 match ch {
12042 ' ' => {
12043 // Keep track of spaces. Append \t when we reach tab_size
12044 spaces_count += 1;
12045 changed = true;
12046 if spaces_count == tab_size {
12047 reindented_line.push('\t');
12048 spaces_count = 0;
12049 }
12050 }
12051 '\t' => {
12052 reindented_line.push('\t');
12053 spaces_count = 0;
12054 }
12055 _ => {
12056 // Dont append it yet, we might have remaining spaces
12057 first_non_indent_char = Some(ch);
12058 break;
12059 }
12060 }
12061 }
12062
12063 if !changed {
12064 reindented_line.clear();
12065 continue;
12066 }
12067 // Remaining spaces that didn't make a full tab stop
12068 if spaces_count > 0 {
12069 reindented_line.extend(&space_cache[spaces_count - 1]);
12070 }
12071 // If we consume an extra character that was not indentation, add it back
12072 if let Some(extra_char) = first_non_indent_char {
12073 reindented_line.push(extra_char);
12074 }
12075 // Append the rest of the line and replace old reference with new one
12076 reindented_line.extend(chars);
12077 *line = Cow::Owned(reindented_line.clone());
12078 reindented_line.clear();
12079 }
12080 });
12081 }
12082
12083 pub fn convert_to_upper_case(
12084 &mut self,
12085 _: &ConvertToUpperCase,
12086 window: &mut Window,
12087 cx: &mut Context<Self>,
12088 ) {
12089 self.manipulate_text(window, cx, |text| text.to_uppercase())
12090 }
12091
12092 pub fn convert_to_lower_case(
12093 &mut self,
12094 _: &ConvertToLowerCase,
12095 window: &mut Window,
12096 cx: &mut Context<Self>,
12097 ) {
12098 self.manipulate_text(window, cx, |text| text.to_lowercase())
12099 }
12100
12101 pub fn convert_to_title_case(
12102 &mut self,
12103 _: &ConvertToTitleCase,
12104 window: &mut Window,
12105 cx: &mut Context<Self>,
12106 ) {
12107 self.manipulate_text(window, cx, |text| {
12108 text.split('\n')
12109 .map(|line| line.to_case(Case::Title))
12110 .join("\n")
12111 })
12112 }
12113
12114 pub fn convert_to_snake_case(
12115 &mut self,
12116 _: &ConvertToSnakeCase,
12117 window: &mut Window,
12118 cx: &mut Context<Self>,
12119 ) {
12120 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12121 }
12122
12123 pub fn convert_to_kebab_case(
12124 &mut self,
12125 _: &ConvertToKebabCase,
12126 window: &mut Window,
12127 cx: &mut Context<Self>,
12128 ) {
12129 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12130 }
12131
12132 pub fn convert_to_upper_camel_case(
12133 &mut self,
12134 _: &ConvertToUpperCamelCase,
12135 window: &mut Window,
12136 cx: &mut Context<Self>,
12137 ) {
12138 self.manipulate_text(window, cx, |text| {
12139 text.split('\n')
12140 .map(|line| line.to_case(Case::UpperCamel))
12141 .join("\n")
12142 })
12143 }
12144
12145 pub fn convert_to_lower_camel_case(
12146 &mut self,
12147 _: &ConvertToLowerCamelCase,
12148 window: &mut Window,
12149 cx: &mut Context<Self>,
12150 ) {
12151 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12152 }
12153
12154 pub fn convert_to_opposite_case(
12155 &mut self,
12156 _: &ConvertToOppositeCase,
12157 window: &mut Window,
12158 cx: &mut Context<Self>,
12159 ) {
12160 self.manipulate_text(window, cx, |text| {
12161 text.chars()
12162 .fold(String::with_capacity(text.len()), |mut t, c| {
12163 if c.is_uppercase() {
12164 t.extend(c.to_lowercase());
12165 } else {
12166 t.extend(c.to_uppercase());
12167 }
12168 t
12169 })
12170 })
12171 }
12172
12173 pub fn convert_to_sentence_case(
12174 &mut self,
12175 _: &ConvertToSentenceCase,
12176 window: &mut Window,
12177 cx: &mut Context<Self>,
12178 ) {
12179 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12180 }
12181
12182 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12183 self.manipulate_text(window, cx, |text| {
12184 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12185 if has_upper_case_characters {
12186 text.to_lowercase()
12187 } else {
12188 text.to_uppercase()
12189 }
12190 })
12191 }
12192
12193 pub fn convert_to_rot13(
12194 &mut self,
12195 _: &ConvertToRot13,
12196 window: &mut Window,
12197 cx: &mut Context<Self>,
12198 ) {
12199 self.manipulate_text(window, cx, |text| {
12200 text.chars()
12201 .map(|c| match c {
12202 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12203 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12204 _ => c,
12205 })
12206 .collect()
12207 })
12208 }
12209
12210 pub fn convert_to_rot47(
12211 &mut self,
12212 _: &ConvertToRot47,
12213 window: &mut Window,
12214 cx: &mut Context<Self>,
12215 ) {
12216 self.manipulate_text(window, cx, |text| {
12217 text.chars()
12218 .map(|c| {
12219 let code_point = c as u32;
12220 if code_point >= 33 && code_point <= 126 {
12221 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12222 }
12223 c
12224 })
12225 .collect()
12226 })
12227 }
12228
12229 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12230 where
12231 Fn: FnMut(&str) -> String,
12232 {
12233 let buffer = self.buffer.read(cx).snapshot(cx);
12234
12235 let mut new_selections = Vec::new();
12236 let mut edits = Vec::new();
12237 let mut selection_adjustment = 0isize;
12238
12239 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12240 let selection_is_empty = selection.is_empty();
12241
12242 let (start, end) = if selection_is_empty {
12243 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12244 (word_range.start, word_range.end)
12245 } else {
12246 (
12247 buffer.point_to_offset(selection.start),
12248 buffer.point_to_offset(selection.end),
12249 )
12250 };
12251
12252 let text = buffer.text_for_range(start..end).collect::<String>();
12253 let old_length = text.len() as isize;
12254 let text = callback(&text);
12255
12256 new_selections.push(Selection {
12257 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12258 end: MultiBufferOffset(
12259 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12260 ),
12261 goal: SelectionGoal::None,
12262 id: selection.id,
12263 reversed: selection.reversed,
12264 });
12265
12266 selection_adjustment += old_length - text.len() as isize;
12267
12268 edits.push((start..end, text));
12269 }
12270
12271 self.transact(window, cx, |this, window, cx| {
12272 this.buffer.update(cx, |buffer, cx| {
12273 buffer.edit(edits, None, cx);
12274 });
12275
12276 this.change_selections(Default::default(), window, cx, |s| {
12277 s.select(new_selections);
12278 });
12279
12280 this.request_autoscroll(Autoscroll::fit(), cx);
12281 });
12282 }
12283
12284 pub fn move_selection_on_drop(
12285 &mut self,
12286 selection: &Selection<Anchor>,
12287 target: DisplayPoint,
12288 is_cut: bool,
12289 window: &mut Window,
12290 cx: &mut Context<Self>,
12291 ) {
12292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12293 let buffer = display_map.buffer_snapshot();
12294 let mut edits = Vec::new();
12295 let insert_point = display_map
12296 .clip_point(target, Bias::Left)
12297 .to_point(&display_map);
12298 let text = buffer
12299 .text_for_range(selection.start..selection.end)
12300 .collect::<String>();
12301 if is_cut {
12302 edits.push(((selection.start..selection.end), String::new()));
12303 }
12304 let insert_anchor = buffer.anchor_before(insert_point);
12305 edits.push(((insert_anchor..insert_anchor), text));
12306 let last_edit_start = insert_anchor.bias_left(buffer);
12307 let last_edit_end = insert_anchor.bias_right(buffer);
12308 self.transact(window, cx, |this, window, cx| {
12309 this.buffer.update(cx, |buffer, cx| {
12310 buffer.edit(edits, None, cx);
12311 });
12312 this.change_selections(Default::default(), window, cx, |s| {
12313 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12314 });
12315 });
12316 }
12317
12318 pub fn clear_selection_drag_state(&mut self) {
12319 self.selection_drag_state = SelectionDragState::None;
12320 }
12321
12322 pub fn duplicate(
12323 &mut self,
12324 upwards: bool,
12325 whole_lines: bool,
12326 window: &mut Window,
12327 cx: &mut Context<Self>,
12328 ) {
12329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12330
12331 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12332 let buffer = display_map.buffer_snapshot();
12333 let selections = self.selections.all::<Point>(&display_map);
12334
12335 let mut edits = Vec::new();
12336 let mut selections_iter = selections.iter().peekable();
12337 while let Some(selection) = selections_iter.next() {
12338 let mut rows = selection.spanned_rows(false, &display_map);
12339 // duplicate line-wise
12340 if whole_lines || selection.start == selection.end {
12341 // Avoid duplicating the same lines twice.
12342 while let Some(next_selection) = selections_iter.peek() {
12343 let next_rows = next_selection.spanned_rows(false, &display_map);
12344 if next_rows.start < rows.end {
12345 rows.end = next_rows.end;
12346 selections_iter.next().unwrap();
12347 } else {
12348 break;
12349 }
12350 }
12351
12352 // Copy the text from the selected row region and splice it either at the start
12353 // or end of the region.
12354 let start = Point::new(rows.start.0, 0);
12355 let end = Point::new(
12356 rows.end.previous_row().0,
12357 buffer.line_len(rows.end.previous_row()),
12358 );
12359
12360 let mut text = buffer.text_for_range(start..end).collect::<String>();
12361
12362 let insert_location = if upwards {
12363 // When duplicating upward, we need to insert before the current line.
12364 // If we're on the last line and it doesn't end with a newline,
12365 // we need to add a newline before the duplicated content.
12366 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12367 && buffer.max_point().column > 0
12368 && !text.ends_with('\n');
12369
12370 if needs_leading_newline {
12371 text.insert(0, '\n');
12372 end
12373 } else {
12374 text.push('\n');
12375 Point::new(rows.start.0, 0)
12376 }
12377 } else {
12378 text.push('\n');
12379 start
12380 };
12381 edits.push((insert_location..insert_location, text));
12382 } else {
12383 // duplicate character-wise
12384 let start = selection.start;
12385 let end = selection.end;
12386 let text = buffer.text_for_range(start..end).collect::<String>();
12387 edits.push((selection.end..selection.end, text));
12388 }
12389 }
12390
12391 self.transact(window, cx, |this, window, cx| {
12392 this.buffer.update(cx, |buffer, cx| {
12393 buffer.edit(edits, None, cx);
12394 });
12395
12396 // When duplicating upward with whole lines, move the cursor to the duplicated line
12397 if upwards && whole_lines {
12398 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12399
12400 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12401 let mut new_ranges = Vec::new();
12402 let selections = s.all::<Point>(&display_map);
12403 let mut selections_iter = selections.iter().peekable();
12404
12405 while let Some(first_selection) = selections_iter.next() {
12406 // Group contiguous selections together to find the total row span
12407 let mut group_selections = vec![first_selection];
12408 let mut rows = first_selection.spanned_rows(false, &display_map);
12409
12410 while let Some(next_selection) = selections_iter.peek() {
12411 let next_rows = next_selection.spanned_rows(false, &display_map);
12412 if next_rows.start < rows.end {
12413 rows.end = next_rows.end;
12414 group_selections.push(selections_iter.next().unwrap());
12415 } else {
12416 break;
12417 }
12418 }
12419
12420 let row_count = rows.end.0 - rows.start.0;
12421
12422 // Move all selections in this group up by the total number of duplicated rows
12423 for selection in group_selections {
12424 let new_start = Point::new(
12425 selection.start.row.saturating_sub(row_count),
12426 selection.start.column,
12427 );
12428
12429 let new_end = Point::new(
12430 selection.end.row.saturating_sub(row_count),
12431 selection.end.column,
12432 );
12433
12434 new_ranges.push(new_start..new_end);
12435 }
12436 }
12437
12438 s.select_ranges(new_ranges);
12439 });
12440 }
12441
12442 this.request_autoscroll(Autoscroll::fit(), cx);
12443 });
12444 }
12445
12446 pub fn duplicate_line_up(
12447 &mut self,
12448 _: &DuplicateLineUp,
12449 window: &mut Window,
12450 cx: &mut Context<Self>,
12451 ) {
12452 self.duplicate(true, true, window, cx);
12453 }
12454
12455 pub fn duplicate_line_down(
12456 &mut self,
12457 _: &DuplicateLineDown,
12458 window: &mut Window,
12459 cx: &mut Context<Self>,
12460 ) {
12461 self.duplicate(false, true, window, cx);
12462 }
12463
12464 pub fn duplicate_selection(
12465 &mut self,
12466 _: &DuplicateSelection,
12467 window: &mut Window,
12468 cx: &mut Context<Self>,
12469 ) {
12470 self.duplicate(false, false, window, cx);
12471 }
12472
12473 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12474 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12475 if self.mode.is_single_line() {
12476 cx.propagate();
12477 return;
12478 }
12479
12480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12481 let buffer = self.buffer.read(cx).snapshot(cx);
12482
12483 let mut edits = Vec::new();
12484 let mut unfold_ranges = Vec::new();
12485 let mut refold_creases = Vec::new();
12486
12487 let selections = self.selections.all::<Point>(&display_map);
12488 let mut selections = selections.iter().peekable();
12489 let mut contiguous_row_selections = Vec::new();
12490 let mut new_selections = Vec::new();
12491
12492 while let Some(selection) = selections.next() {
12493 // Find all the selections that span a contiguous row range
12494 let (start_row, end_row) = consume_contiguous_rows(
12495 &mut contiguous_row_selections,
12496 selection,
12497 &display_map,
12498 &mut selections,
12499 );
12500
12501 // Move the text spanned by the row range to be before the line preceding the row range
12502 if start_row.0 > 0 {
12503 let range_to_move = Point::new(
12504 start_row.previous_row().0,
12505 buffer.line_len(start_row.previous_row()),
12506 )
12507 ..Point::new(
12508 end_row.previous_row().0,
12509 buffer.line_len(end_row.previous_row()),
12510 );
12511 let insertion_point = display_map
12512 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12513 .0;
12514
12515 // Don't move lines across excerpts
12516 if buffer
12517 .excerpt_containing(insertion_point..range_to_move.end)
12518 .is_some()
12519 {
12520 let text = buffer
12521 .text_for_range(range_to_move.clone())
12522 .flat_map(|s| s.chars())
12523 .skip(1)
12524 .chain(['\n'])
12525 .collect::<String>();
12526
12527 edits.push((
12528 buffer.anchor_after(range_to_move.start)
12529 ..buffer.anchor_before(range_to_move.end),
12530 String::new(),
12531 ));
12532 let insertion_anchor = buffer.anchor_after(insertion_point);
12533 edits.push((insertion_anchor..insertion_anchor, text));
12534
12535 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12536
12537 // Move selections up
12538 new_selections.extend(contiguous_row_selections.drain(..).map(
12539 |mut selection| {
12540 selection.start.row -= row_delta;
12541 selection.end.row -= row_delta;
12542 selection
12543 },
12544 ));
12545
12546 // Move folds up
12547 unfold_ranges.push(range_to_move.clone());
12548 for fold in display_map.folds_in_range(
12549 buffer.anchor_before(range_to_move.start)
12550 ..buffer.anchor_after(range_to_move.end),
12551 ) {
12552 let mut start = fold.range.start.to_point(&buffer);
12553 let mut end = fold.range.end.to_point(&buffer);
12554 start.row -= row_delta;
12555 end.row -= row_delta;
12556 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12557 }
12558 }
12559 }
12560
12561 // If we didn't move line(s), preserve the existing selections
12562 new_selections.append(&mut contiguous_row_selections);
12563 }
12564
12565 self.transact(window, cx, |this, window, cx| {
12566 this.unfold_ranges(&unfold_ranges, true, true, cx);
12567 this.buffer.update(cx, |buffer, cx| {
12568 for (range, text) in edits {
12569 buffer.edit([(range, text)], None, cx);
12570 }
12571 });
12572 this.fold_creases(refold_creases, true, window, cx);
12573 this.change_selections(Default::default(), window, cx, |s| {
12574 s.select(new_selections);
12575 })
12576 });
12577 }
12578
12579 pub fn move_line_down(
12580 &mut self,
12581 _: &MoveLineDown,
12582 window: &mut Window,
12583 cx: &mut Context<Self>,
12584 ) {
12585 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12586 if self.mode.is_single_line() {
12587 cx.propagate();
12588 return;
12589 }
12590
12591 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12592 let buffer = self.buffer.read(cx).snapshot(cx);
12593
12594 let mut edits = Vec::new();
12595 let mut unfold_ranges = Vec::new();
12596 let mut refold_creases = Vec::new();
12597
12598 let selections = self.selections.all::<Point>(&display_map);
12599 let mut selections = selections.iter().peekable();
12600 let mut contiguous_row_selections = Vec::new();
12601 let mut new_selections = Vec::new();
12602
12603 while let Some(selection) = selections.next() {
12604 // Find all the selections that span a contiguous row range
12605 let (start_row, end_row) = consume_contiguous_rows(
12606 &mut contiguous_row_selections,
12607 selection,
12608 &display_map,
12609 &mut selections,
12610 );
12611
12612 // Move the text spanned by the row range to be after the last line of the row range
12613 if end_row.0 <= buffer.max_point().row {
12614 let range_to_move =
12615 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12616 let insertion_point = display_map
12617 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12618 .0;
12619
12620 // Don't move lines across excerpt boundaries
12621 if buffer
12622 .excerpt_containing(range_to_move.start..insertion_point)
12623 .is_some()
12624 {
12625 let mut text = String::from("\n");
12626 text.extend(buffer.text_for_range(range_to_move.clone()));
12627 text.pop(); // Drop trailing newline
12628 edits.push((
12629 buffer.anchor_after(range_to_move.start)
12630 ..buffer.anchor_before(range_to_move.end),
12631 String::new(),
12632 ));
12633 let insertion_anchor = buffer.anchor_after(insertion_point);
12634 edits.push((insertion_anchor..insertion_anchor, text));
12635
12636 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12637
12638 // Move selections down
12639 new_selections.extend(contiguous_row_selections.drain(..).map(
12640 |mut selection| {
12641 selection.start.row += row_delta;
12642 selection.end.row += row_delta;
12643 selection
12644 },
12645 ));
12646
12647 // Move folds down
12648 unfold_ranges.push(range_to_move.clone());
12649 for fold in display_map.folds_in_range(
12650 buffer.anchor_before(range_to_move.start)
12651 ..buffer.anchor_after(range_to_move.end),
12652 ) {
12653 let mut start = fold.range.start.to_point(&buffer);
12654 let mut end = fold.range.end.to_point(&buffer);
12655 start.row += row_delta;
12656 end.row += row_delta;
12657 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12658 }
12659 }
12660 }
12661
12662 // If we didn't move line(s), preserve the existing selections
12663 new_selections.append(&mut contiguous_row_selections);
12664 }
12665
12666 self.transact(window, cx, |this, window, cx| {
12667 this.unfold_ranges(&unfold_ranges, true, true, cx);
12668 this.buffer.update(cx, |buffer, cx| {
12669 for (range, text) in edits {
12670 buffer.edit([(range, text)], None, cx);
12671 }
12672 });
12673 this.fold_creases(refold_creases, true, window, cx);
12674 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12675 });
12676 }
12677
12678 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12680 let text_layout_details = &self.text_layout_details(window);
12681 self.transact(window, cx, |this, window, cx| {
12682 let edits = this.change_selections(Default::default(), window, cx, |s| {
12683 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12684 s.move_with(|display_map, selection| {
12685 if !selection.is_empty() {
12686 return;
12687 }
12688
12689 let mut head = selection.head();
12690 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12691 if head.column() == display_map.line_len(head.row()) {
12692 transpose_offset = display_map
12693 .buffer_snapshot()
12694 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12695 }
12696
12697 if transpose_offset == MultiBufferOffset(0) {
12698 return;
12699 }
12700
12701 *head.column_mut() += 1;
12702 head = display_map.clip_point(head, Bias::Right);
12703 let goal = SelectionGoal::HorizontalPosition(
12704 display_map
12705 .x_for_display_point(head, text_layout_details)
12706 .into(),
12707 );
12708 selection.collapse_to(head, goal);
12709
12710 let transpose_start = display_map
12711 .buffer_snapshot()
12712 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12713 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12714 let transpose_end = display_map
12715 .buffer_snapshot()
12716 .clip_offset(transpose_offset + 1usize, Bias::Right);
12717 if let Some(ch) = display_map
12718 .buffer_snapshot()
12719 .chars_at(transpose_start)
12720 .next()
12721 {
12722 edits.push((transpose_start..transpose_offset, String::new()));
12723 edits.push((transpose_end..transpose_end, ch.to_string()));
12724 }
12725 }
12726 });
12727 edits
12728 });
12729 this.buffer
12730 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12731 let selections = this
12732 .selections
12733 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12734 this.change_selections(Default::default(), window, cx, |s| {
12735 s.select(selections);
12736 });
12737 });
12738 }
12739
12740 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12741 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12742 if self.mode.is_single_line() {
12743 cx.propagate();
12744 return;
12745 }
12746
12747 self.rewrap_impl(RewrapOptions::default(), cx)
12748 }
12749
12750 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12751 let buffer = self.buffer.read(cx).snapshot(cx);
12752 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12753
12754 #[derive(Clone, Debug, PartialEq)]
12755 enum CommentFormat {
12756 /// single line comment, with prefix for line
12757 Line(String),
12758 /// single line within a block comment, with prefix for line
12759 BlockLine(String),
12760 /// a single line of a block comment that includes the initial delimiter
12761 BlockCommentWithStart(BlockCommentConfig),
12762 /// a single line of a block comment that includes the ending delimiter
12763 BlockCommentWithEnd(BlockCommentConfig),
12764 }
12765
12766 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12767 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12768 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12769 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12770 .peekable();
12771
12772 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12773 row
12774 } else {
12775 return Vec::new();
12776 };
12777
12778 let language_settings = buffer.language_settings_at(selection.head(), cx);
12779 let language_scope = buffer.language_scope_at(selection.head());
12780
12781 let indent_and_prefix_for_row =
12782 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12783 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12784 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12785 &language_scope
12786 {
12787 let indent_end = Point::new(row, indent.len);
12788 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12789 let line_text_after_indent = buffer
12790 .text_for_range(indent_end..line_end)
12791 .collect::<String>();
12792
12793 let is_within_comment_override = buffer
12794 .language_scope_at(indent_end)
12795 .is_some_and(|scope| scope.override_name() == Some("comment"));
12796 let comment_delimiters = if is_within_comment_override {
12797 // we are within a comment syntax node, but we don't
12798 // yet know what kind of comment: block, doc or line
12799 match (
12800 language_scope.documentation_comment(),
12801 language_scope.block_comment(),
12802 ) {
12803 (Some(config), _) | (_, Some(config))
12804 if buffer.contains_str_at(indent_end, &config.start) =>
12805 {
12806 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12807 }
12808 (Some(config), _) | (_, Some(config))
12809 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12810 {
12811 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12812 }
12813 (Some(config), _) | (_, Some(config))
12814 if buffer.contains_str_at(indent_end, &config.prefix) =>
12815 {
12816 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12817 }
12818 (_, _) => language_scope
12819 .line_comment_prefixes()
12820 .iter()
12821 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12822 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12823 }
12824 } else {
12825 // we not in an overridden comment node, but we may
12826 // be within a non-overridden line comment node
12827 language_scope
12828 .line_comment_prefixes()
12829 .iter()
12830 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12831 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12832 };
12833
12834 let rewrap_prefix = language_scope
12835 .rewrap_prefixes()
12836 .iter()
12837 .find_map(|prefix_regex| {
12838 prefix_regex.find(&line_text_after_indent).map(|mat| {
12839 if mat.start() == 0 {
12840 Some(mat.as_str().to_string())
12841 } else {
12842 None
12843 }
12844 })
12845 })
12846 .flatten();
12847 (comment_delimiters, rewrap_prefix)
12848 } else {
12849 (None, None)
12850 };
12851 (indent, comment_prefix, rewrap_prefix)
12852 };
12853
12854 let mut ranges = Vec::new();
12855 let from_empty_selection = selection.is_empty();
12856
12857 let mut current_range_start = first_row;
12858 let mut prev_row = first_row;
12859 let (
12860 mut current_range_indent,
12861 mut current_range_comment_delimiters,
12862 mut current_range_rewrap_prefix,
12863 ) = indent_and_prefix_for_row(first_row);
12864
12865 for row in non_blank_rows_iter.skip(1) {
12866 let has_paragraph_break = row > prev_row + 1;
12867
12868 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12869 indent_and_prefix_for_row(row);
12870
12871 let has_indent_change = row_indent != current_range_indent;
12872 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12873
12874 let has_boundary_change = has_comment_change
12875 || row_rewrap_prefix.is_some()
12876 || (has_indent_change && current_range_comment_delimiters.is_some());
12877
12878 if has_paragraph_break || has_boundary_change {
12879 ranges.push((
12880 language_settings.clone(),
12881 Point::new(current_range_start, 0)
12882 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12883 current_range_indent,
12884 current_range_comment_delimiters.clone(),
12885 current_range_rewrap_prefix.clone(),
12886 from_empty_selection,
12887 ));
12888 current_range_start = row;
12889 current_range_indent = row_indent;
12890 current_range_comment_delimiters = row_comment_delimiters;
12891 current_range_rewrap_prefix = row_rewrap_prefix;
12892 }
12893 prev_row = row;
12894 }
12895
12896 ranges.push((
12897 language_settings.clone(),
12898 Point::new(current_range_start, 0)
12899 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12900 current_range_indent,
12901 current_range_comment_delimiters,
12902 current_range_rewrap_prefix,
12903 from_empty_selection,
12904 ));
12905
12906 ranges
12907 });
12908
12909 let mut edits = Vec::new();
12910 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12911
12912 for (
12913 language_settings,
12914 wrap_range,
12915 mut indent_size,
12916 comment_prefix,
12917 rewrap_prefix,
12918 from_empty_selection,
12919 ) in wrap_ranges
12920 {
12921 let mut start_row = wrap_range.start.row;
12922 let mut end_row = wrap_range.end.row;
12923
12924 // Skip selections that overlap with a range that has already been rewrapped.
12925 let selection_range = start_row..end_row;
12926 if rewrapped_row_ranges
12927 .iter()
12928 .any(|range| range.overlaps(&selection_range))
12929 {
12930 continue;
12931 }
12932
12933 let tab_size = language_settings.tab_size;
12934
12935 let (line_prefix, inside_comment) = match &comment_prefix {
12936 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12937 (Some(prefix.as_str()), true)
12938 }
12939 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12940 (Some(prefix.as_ref()), true)
12941 }
12942 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12943 start: _,
12944 end: _,
12945 prefix,
12946 tab_size,
12947 })) => {
12948 indent_size.len += tab_size;
12949 (Some(prefix.as_ref()), true)
12950 }
12951 None => (None, false),
12952 };
12953 let indent_prefix = indent_size.chars().collect::<String>();
12954 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12955
12956 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12957 RewrapBehavior::InComments => inside_comment,
12958 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12959 RewrapBehavior::Anywhere => true,
12960 };
12961
12962 let should_rewrap = options.override_language_settings
12963 || allow_rewrap_based_on_language
12964 || self.hard_wrap.is_some();
12965 if !should_rewrap {
12966 continue;
12967 }
12968
12969 if from_empty_selection {
12970 'expand_upwards: while start_row > 0 {
12971 let prev_row = start_row - 1;
12972 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12973 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12974 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12975 {
12976 start_row = prev_row;
12977 } else {
12978 break 'expand_upwards;
12979 }
12980 }
12981
12982 'expand_downwards: while end_row < buffer.max_point().row {
12983 let next_row = end_row + 1;
12984 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12985 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12986 && !buffer.is_line_blank(MultiBufferRow(next_row))
12987 {
12988 end_row = next_row;
12989 } else {
12990 break 'expand_downwards;
12991 }
12992 }
12993 }
12994
12995 let start = Point::new(start_row, 0);
12996 let start_offset = ToOffset::to_offset(&start, &buffer);
12997 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12998 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12999 let mut first_line_delimiter = None;
13000 let mut last_line_delimiter = None;
13001 let Some(lines_without_prefixes) = selection_text
13002 .lines()
13003 .enumerate()
13004 .map(|(ix, line)| {
13005 let line_trimmed = line.trim_start();
13006 if rewrap_prefix.is_some() && ix > 0 {
13007 Ok(line_trimmed)
13008 } else if let Some(
13009 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13010 start,
13011 prefix,
13012 end,
13013 tab_size,
13014 })
13015 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13016 start,
13017 prefix,
13018 end,
13019 tab_size,
13020 }),
13021 ) = &comment_prefix
13022 {
13023 let line_trimmed = line_trimmed
13024 .strip_prefix(start.as_ref())
13025 .map(|s| {
13026 let mut indent_size = indent_size;
13027 indent_size.len -= tab_size;
13028 let indent_prefix: String = indent_size.chars().collect();
13029 first_line_delimiter = Some((indent_prefix, start));
13030 s.trim_start()
13031 })
13032 .unwrap_or(line_trimmed);
13033 let line_trimmed = line_trimmed
13034 .strip_suffix(end.as_ref())
13035 .map(|s| {
13036 last_line_delimiter = Some(end);
13037 s.trim_end()
13038 })
13039 .unwrap_or(line_trimmed);
13040 let line_trimmed = line_trimmed
13041 .strip_prefix(prefix.as_ref())
13042 .unwrap_or(line_trimmed);
13043 Ok(line_trimmed)
13044 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13045 line_trimmed.strip_prefix(prefix).with_context(|| {
13046 format!("line did not start with prefix {prefix:?}: {line:?}")
13047 })
13048 } else {
13049 line_trimmed
13050 .strip_prefix(&line_prefix.trim_start())
13051 .with_context(|| {
13052 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13053 })
13054 }
13055 })
13056 .collect::<Result<Vec<_>, _>>()
13057 .log_err()
13058 else {
13059 continue;
13060 };
13061
13062 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13063 buffer
13064 .language_settings_at(Point::new(start_row, 0), cx)
13065 .preferred_line_length as usize
13066 });
13067
13068 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13069 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13070 } else {
13071 line_prefix.clone()
13072 };
13073
13074 let wrapped_text = {
13075 let mut wrapped_text = wrap_with_prefix(
13076 line_prefix,
13077 subsequent_lines_prefix,
13078 lines_without_prefixes.join("\n"),
13079 wrap_column,
13080 tab_size,
13081 options.preserve_existing_whitespace,
13082 );
13083
13084 if let Some((indent, delimiter)) = first_line_delimiter {
13085 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13086 }
13087 if let Some(last_line) = last_line_delimiter {
13088 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13089 }
13090
13091 wrapped_text
13092 };
13093
13094 // TODO: should always use char-based diff while still supporting cursor behavior that
13095 // matches vim.
13096 let mut diff_options = DiffOptions::default();
13097 if options.override_language_settings {
13098 diff_options.max_word_diff_len = 0;
13099 diff_options.max_word_diff_line_count = 0;
13100 } else {
13101 diff_options.max_word_diff_len = usize::MAX;
13102 diff_options.max_word_diff_line_count = usize::MAX;
13103 }
13104
13105 for (old_range, new_text) in
13106 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13107 {
13108 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13109 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13110 edits.push((edit_start..edit_end, new_text));
13111 }
13112
13113 rewrapped_row_ranges.push(start_row..=end_row);
13114 }
13115
13116 self.buffer
13117 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13118 }
13119
13120 pub fn cut_common(
13121 &mut self,
13122 cut_no_selection_line: bool,
13123 window: &mut Window,
13124 cx: &mut Context<Self>,
13125 ) -> ClipboardItem {
13126 let mut text = String::new();
13127 let buffer = self.buffer.read(cx).snapshot(cx);
13128 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13129 let mut clipboard_selections = Vec::with_capacity(selections.len());
13130 {
13131 let max_point = buffer.max_point();
13132 let mut is_first = true;
13133 let mut prev_selection_was_entire_line = false;
13134 for selection in &mut selections {
13135 let is_entire_line =
13136 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13137 if is_entire_line {
13138 selection.start = Point::new(selection.start.row, 0);
13139 if !selection.is_empty() && selection.end.column == 0 {
13140 selection.end = cmp::min(max_point, selection.end);
13141 } else {
13142 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13143 }
13144 selection.goal = SelectionGoal::None;
13145 }
13146 if is_first {
13147 is_first = false;
13148 } else if !prev_selection_was_entire_line {
13149 text += "\n";
13150 }
13151 prev_selection_was_entire_line = is_entire_line;
13152 let mut len = 0;
13153 for chunk in buffer.text_for_range(selection.start..selection.end) {
13154 text.push_str(chunk);
13155 len += chunk.len();
13156 }
13157
13158 clipboard_selections.push(ClipboardSelection::for_buffer(
13159 len,
13160 is_entire_line,
13161 selection.range(),
13162 &buffer,
13163 self.project.as_ref(),
13164 cx,
13165 ));
13166 }
13167 }
13168
13169 self.transact(window, cx, |this, window, cx| {
13170 this.change_selections(Default::default(), window, cx, |s| {
13171 s.select(selections);
13172 });
13173 this.insert("", window, cx);
13174 });
13175 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13176 }
13177
13178 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13179 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13180 let item = self.cut_common(true, window, cx);
13181 cx.write_to_clipboard(item);
13182 }
13183
13184 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13185 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13186 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13187 s.move_with(|snapshot, sel| {
13188 if sel.is_empty() {
13189 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13190 }
13191 if sel.is_empty() {
13192 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13193 }
13194 });
13195 });
13196 let item = self.cut_common(false, window, cx);
13197 cx.set_global(KillRing(item))
13198 }
13199
13200 pub fn kill_ring_yank(
13201 &mut self,
13202 _: &KillRingYank,
13203 window: &mut Window,
13204 cx: &mut Context<Self>,
13205 ) {
13206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13207 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13208 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13209 (kill_ring.text().to_string(), kill_ring.metadata_json())
13210 } else {
13211 return;
13212 }
13213 } else {
13214 return;
13215 };
13216 self.do_paste(&text, metadata, false, window, cx);
13217 }
13218
13219 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13220 self.do_copy(true, cx);
13221 }
13222
13223 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13224 self.do_copy(false, cx);
13225 }
13226
13227 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13228 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13229 let buffer = self.buffer.read(cx).read(cx);
13230 let mut text = String::new();
13231
13232 let mut clipboard_selections = Vec::with_capacity(selections.len());
13233 {
13234 let max_point = buffer.max_point();
13235 let mut is_first = true;
13236 let mut prev_selection_was_entire_line = false;
13237 for selection in &selections {
13238 let mut start = selection.start;
13239 let mut end = selection.end;
13240 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13241 let mut add_trailing_newline = false;
13242 if is_entire_line {
13243 start = Point::new(start.row, 0);
13244 let next_line_start = Point::new(end.row + 1, 0);
13245 if next_line_start <= max_point {
13246 end = next_line_start;
13247 } else {
13248 // We're on the last line without a trailing newline.
13249 // Copy to the end of the line and add a newline afterwards.
13250 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13251 add_trailing_newline = true;
13252 }
13253 }
13254
13255 let mut trimmed_selections = Vec::new();
13256 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13257 let row = MultiBufferRow(start.row);
13258 let first_indent = buffer.indent_size_for_line(row);
13259 if first_indent.len == 0 || start.column > first_indent.len {
13260 trimmed_selections.push(start..end);
13261 } else {
13262 trimmed_selections.push(
13263 Point::new(row.0, first_indent.len)
13264 ..Point::new(row.0, buffer.line_len(row)),
13265 );
13266 for row in start.row + 1..=end.row {
13267 let mut line_len = buffer.line_len(MultiBufferRow(row));
13268 if row == end.row {
13269 line_len = end.column;
13270 }
13271 if line_len == 0 {
13272 trimmed_selections
13273 .push(Point::new(row, 0)..Point::new(row, line_len));
13274 continue;
13275 }
13276 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13277 if row_indent_size.len >= first_indent.len {
13278 trimmed_selections.push(
13279 Point::new(row, first_indent.len)..Point::new(row, line_len),
13280 );
13281 } else {
13282 trimmed_selections.clear();
13283 trimmed_selections.push(start..end);
13284 break;
13285 }
13286 }
13287 }
13288 } else {
13289 trimmed_selections.push(start..end);
13290 }
13291
13292 let is_multiline_trim = trimmed_selections.len() > 1;
13293 for trimmed_range in trimmed_selections {
13294 if is_first {
13295 is_first = false;
13296 } else if is_multiline_trim || !prev_selection_was_entire_line {
13297 text += "\n";
13298 }
13299 prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13300 let mut len = 0;
13301 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13302 text.push_str(chunk);
13303 len += chunk.len();
13304 }
13305 if add_trailing_newline {
13306 text.push('\n');
13307 len += 1;
13308 }
13309 clipboard_selections.push(ClipboardSelection::for_buffer(
13310 len,
13311 is_entire_line,
13312 trimmed_range,
13313 &buffer,
13314 self.project.as_ref(),
13315 cx,
13316 ));
13317 }
13318 }
13319 }
13320
13321 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13322 text,
13323 clipboard_selections,
13324 ));
13325 }
13326
13327 pub fn do_paste(
13328 &mut self,
13329 text: &String,
13330 clipboard_selections: Option<Vec<ClipboardSelection>>,
13331 handle_entire_lines: bool,
13332 window: &mut Window,
13333 cx: &mut Context<Self>,
13334 ) {
13335 if self.read_only(cx) {
13336 return;
13337 }
13338
13339 let clipboard_text = Cow::Borrowed(text.as_str());
13340
13341 self.transact(window, cx, |this, window, cx| {
13342 let had_active_edit_prediction = this.has_active_edit_prediction();
13343 let display_map = this.display_snapshot(cx);
13344 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13345 let cursor_offset = this
13346 .selections
13347 .last::<MultiBufferOffset>(&display_map)
13348 .head();
13349
13350 if let Some(mut clipboard_selections) = clipboard_selections {
13351 let all_selections_were_entire_line =
13352 clipboard_selections.iter().all(|s| s.is_entire_line);
13353 let first_selection_indent_column =
13354 clipboard_selections.first().map(|s| s.first_line_indent);
13355 if clipboard_selections.len() != old_selections.len() {
13356 clipboard_selections.drain(..);
13357 }
13358 let mut auto_indent_on_paste = true;
13359
13360 this.buffer.update(cx, |buffer, cx| {
13361 let snapshot = buffer.read(cx);
13362 auto_indent_on_paste = snapshot
13363 .language_settings_at(cursor_offset, cx)
13364 .auto_indent_on_paste;
13365
13366 let mut start_offset = 0;
13367 let mut edits = Vec::new();
13368 let mut original_indent_columns = Vec::new();
13369 for (ix, selection) in old_selections.iter().enumerate() {
13370 let to_insert;
13371 let entire_line;
13372 let original_indent_column;
13373 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13374 let end_offset = start_offset + clipboard_selection.len;
13375 to_insert = &clipboard_text[start_offset..end_offset];
13376 entire_line = clipboard_selection.is_entire_line;
13377 start_offset = if entire_line {
13378 end_offset
13379 } else {
13380 end_offset + 1
13381 };
13382 original_indent_column = Some(clipboard_selection.first_line_indent);
13383 } else {
13384 to_insert = &*clipboard_text;
13385 entire_line = all_selections_were_entire_line;
13386 original_indent_column = first_selection_indent_column
13387 }
13388
13389 let (range, to_insert) =
13390 if selection.is_empty() && handle_entire_lines && entire_line {
13391 // If the corresponding selection was empty when this slice of the
13392 // clipboard text was written, then the entire line containing the
13393 // selection was copied. If this selection is also currently empty,
13394 // then paste the line before the current line of the buffer.
13395 let column = selection.start.to_point(&snapshot).column as usize;
13396 let line_start = selection.start - column;
13397 (line_start..line_start, Cow::Borrowed(to_insert))
13398 } else {
13399 let language = snapshot.language_at(selection.head());
13400 let range = selection.range();
13401 if let Some(language) = language
13402 && language.name() == "Markdown".into()
13403 {
13404 edit_for_markdown_paste(
13405 &snapshot,
13406 range,
13407 to_insert,
13408 url::Url::parse(to_insert).ok(),
13409 )
13410 } else {
13411 (range, Cow::Borrowed(to_insert))
13412 }
13413 };
13414
13415 edits.push((range, to_insert));
13416 original_indent_columns.push(original_indent_column);
13417 }
13418 drop(snapshot);
13419
13420 buffer.edit(
13421 edits,
13422 if auto_indent_on_paste {
13423 Some(AutoindentMode::Block {
13424 original_indent_columns,
13425 })
13426 } else {
13427 None
13428 },
13429 cx,
13430 );
13431 });
13432
13433 let selections = this
13434 .selections
13435 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13436 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13437 } else {
13438 let url = url::Url::parse(&clipboard_text).ok();
13439
13440 let auto_indent_mode = if !clipboard_text.is_empty() {
13441 Some(AutoindentMode::Block {
13442 original_indent_columns: Vec::new(),
13443 })
13444 } else {
13445 None
13446 };
13447
13448 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13449 let snapshot = buffer.snapshot(cx);
13450
13451 let anchors = old_selections
13452 .iter()
13453 .map(|s| {
13454 let anchor = snapshot.anchor_after(s.head());
13455 s.map(|_| anchor)
13456 })
13457 .collect::<Vec<_>>();
13458
13459 let mut edits = Vec::new();
13460
13461 for selection in old_selections.iter() {
13462 let language = snapshot.language_at(selection.head());
13463 let range = selection.range();
13464
13465 let (edit_range, edit_text) = if let Some(language) = language
13466 && language.name() == "Markdown".into()
13467 {
13468 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13469 } else {
13470 (range, clipboard_text.clone())
13471 };
13472
13473 edits.push((edit_range, edit_text));
13474 }
13475
13476 drop(snapshot);
13477 buffer.edit(edits, auto_indent_mode, cx);
13478
13479 anchors
13480 });
13481
13482 this.change_selections(Default::default(), window, cx, |s| {
13483 s.select_anchors(selection_anchors);
13484 });
13485 }
13486
13487 // 🤔 | .. | show_in_menu |
13488 // | .. | true true
13489 // | had_edit_prediction | false true
13490
13491 let trigger_in_words =
13492 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13493
13494 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13495 });
13496 }
13497
13498 pub fn diff_clipboard_with_selection(
13499 &mut self,
13500 _: &DiffClipboardWithSelection,
13501 window: &mut Window,
13502 cx: &mut Context<Self>,
13503 ) {
13504 let selections = self
13505 .selections
13506 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13507
13508 if selections.is_empty() {
13509 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13510 return;
13511 };
13512
13513 let clipboard_text = match cx.read_from_clipboard() {
13514 Some(item) => match item.entries().first() {
13515 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13516 _ => None,
13517 },
13518 None => None,
13519 };
13520
13521 let Some(clipboard_text) = clipboard_text else {
13522 log::warn!("Clipboard doesn't contain text.");
13523 return;
13524 };
13525
13526 window.dispatch_action(
13527 Box::new(DiffClipboardWithSelectionData {
13528 clipboard_text,
13529 editor: cx.entity(),
13530 }),
13531 cx,
13532 );
13533 }
13534
13535 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13536 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13537 if let Some(item) = cx.read_from_clipboard() {
13538 let entries = item.entries();
13539
13540 match entries.first() {
13541 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13542 // of all the pasted entries.
13543 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13544 .do_paste(
13545 clipboard_string.text(),
13546 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13547 true,
13548 window,
13549 cx,
13550 ),
13551 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13552 }
13553 }
13554 }
13555
13556 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13557 if self.read_only(cx) {
13558 return;
13559 }
13560
13561 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13562
13563 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13564 if let Some((selections, _)) =
13565 self.selection_history.transaction(transaction_id).cloned()
13566 {
13567 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13568 s.select_anchors(selections.to_vec());
13569 });
13570 } else {
13571 log::error!(
13572 "No entry in selection_history found for undo. \
13573 This may correspond to a bug where undo does not update the selection. \
13574 If this is occurring, please add details to \
13575 https://github.com/zed-industries/zed/issues/22692"
13576 );
13577 }
13578 self.request_autoscroll(Autoscroll::fit(), cx);
13579 self.unmark_text(window, cx);
13580 self.refresh_edit_prediction(true, false, window, cx);
13581 cx.emit(EditorEvent::Edited { transaction_id });
13582 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13583 }
13584 }
13585
13586 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13587 if self.read_only(cx) {
13588 return;
13589 }
13590
13591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13592
13593 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13594 if let Some((_, Some(selections))) =
13595 self.selection_history.transaction(transaction_id).cloned()
13596 {
13597 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13598 s.select_anchors(selections.to_vec());
13599 });
13600 } else {
13601 log::error!(
13602 "No entry in selection_history found for redo. \
13603 This may correspond to a bug where undo does not update the selection. \
13604 If this is occurring, please add details to \
13605 https://github.com/zed-industries/zed/issues/22692"
13606 );
13607 }
13608 self.request_autoscroll(Autoscroll::fit(), cx);
13609 self.unmark_text(window, cx);
13610 self.refresh_edit_prediction(true, false, window, cx);
13611 cx.emit(EditorEvent::Edited { transaction_id });
13612 }
13613 }
13614
13615 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13616 self.buffer
13617 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13618 }
13619
13620 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13621 self.buffer
13622 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13623 }
13624
13625 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13627 self.change_selections(Default::default(), window, cx, |s| {
13628 s.move_with(|map, selection| {
13629 let cursor = if selection.is_empty() {
13630 movement::left(map, selection.start)
13631 } else {
13632 selection.start
13633 };
13634 selection.collapse_to(cursor, SelectionGoal::None);
13635 });
13636 })
13637 }
13638
13639 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13640 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13641 self.change_selections(Default::default(), window, cx, |s| {
13642 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13643 })
13644 }
13645
13646 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13648 self.change_selections(Default::default(), window, cx, |s| {
13649 s.move_with(|map, selection| {
13650 let cursor = if selection.is_empty() {
13651 movement::right(map, selection.end)
13652 } else {
13653 selection.end
13654 };
13655 selection.collapse_to(cursor, SelectionGoal::None)
13656 });
13657 })
13658 }
13659
13660 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13662 self.change_selections(Default::default(), window, cx, |s| {
13663 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13664 });
13665 }
13666
13667 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13668 if self.take_rename(true, window, cx).is_some() {
13669 return;
13670 }
13671
13672 if self.mode.is_single_line() {
13673 cx.propagate();
13674 return;
13675 }
13676
13677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13678
13679 let text_layout_details = &self.text_layout_details(window);
13680 let selection_count = self.selections.count();
13681 let first_selection = self.selections.first_anchor();
13682
13683 self.change_selections(Default::default(), window, cx, |s| {
13684 s.move_with(|map, selection| {
13685 if !selection.is_empty() {
13686 selection.goal = SelectionGoal::None;
13687 }
13688 let (cursor, goal) = movement::up(
13689 map,
13690 selection.start,
13691 selection.goal,
13692 false,
13693 text_layout_details,
13694 );
13695 selection.collapse_to(cursor, goal);
13696 });
13697 });
13698
13699 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13700 {
13701 cx.propagate();
13702 }
13703 }
13704
13705 pub fn move_up_by_lines(
13706 &mut self,
13707 action: &MoveUpByLines,
13708 window: &mut Window,
13709 cx: &mut Context<Self>,
13710 ) {
13711 if self.take_rename(true, window, cx).is_some() {
13712 return;
13713 }
13714
13715 if self.mode.is_single_line() {
13716 cx.propagate();
13717 return;
13718 }
13719
13720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13721
13722 let text_layout_details = &self.text_layout_details(window);
13723
13724 self.change_selections(Default::default(), window, cx, |s| {
13725 s.move_with(|map, selection| {
13726 if !selection.is_empty() {
13727 selection.goal = SelectionGoal::None;
13728 }
13729 let (cursor, goal) = movement::up_by_rows(
13730 map,
13731 selection.start,
13732 action.lines,
13733 selection.goal,
13734 false,
13735 text_layout_details,
13736 );
13737 selection.collapse_to(cursor, goal);
13738 });
13739 })
13740 }
13741
13742 pub fn move_down_by_lines(
13743 &mut self,
13744 action: &MoveDownByLines,
13745 window: &mut Window,
13746 cx: &mut Context<Self>,
13747 ) {
13748 if self.take_rename(true, window, cx).is_some() {
13749 return;
13750 }
13751
13752 if self.mode.is_single_line() {
13753 cx.propagate();
13754 return;
13755 }
13756
13757 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13758
13759 let text_layout_details = &self.text_layout_details(window);
13760
13761 self.change_selections(Default::default(), window, cx, |s| {
13762 s.move_with(|map, selection| {
13763 if !selection.is_empty() {
13764 selection.goal = SelectionGoal::None;
13765 }
13766 let (cursor, goal) = movement::down_by_rows(
13767 map,
13768 selection.start,
13769 action.lines,
13770 selection.goal,
13771 false,
13772 text_layout_details,
13773 );
13774 selection.collapse_to(cursor, goal);
13775 });
13776 })
13777 }
13778
13779 pub fn select_down_by_lines(
13780 &mut self,
13781 action: &SelectDownByLines,
13782 window: &mut Window,
13783 cx: &mut Context<Self>,
13784 ) {
13785 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13786 let text_layout_details = &self.text_layout_details(window);
13787 self.change_selections(Default::default(), window, cx, |s| {
13788 s.move_heads_with(|map, head, goal| {
13789 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13790 })
13791 })
13792 }
13793
13794 pub fn select_up_by_lines(
13795 &mut self,
13796 action: &SelectUpByLines,
13797 window: &mut Window,
13798 cx: &mut Context<Self>,
13799 ) {
13800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13801 let text_layout_details = &self.text_layout_details(window);
13802 self.change_selections(Default::default(), window, cx, |s| {
13803 s.move_heads_with(|map, head, goal| {
13804 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13805 })
13806 })
13807 }
13808
13809 pub fn select_page_up(
13810 &mut self,
13811 _: &SelectPageUp,
13812 window: &mut Window,
13813 cx: &mut Context<Self>,
13814 ) {
13815 let Some(row_count) = self.visible_row_count() else {
13816 return;
13817 };
13818
13819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13820
13821 let text_layout_details = &self.text_layout_details(window);
13822
13823 self.change_selections(Default::default(), window, cx, |s| {
13824 s.move_heads_with(|map, head, goal| {
13825 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13826 })
13827 })
13828 }
13829
13830 pub fn move_page_up(
13831 &mut self,
13832 action: &MovePageUp,
13833 window: &mut Window,
13834 cx: &mut Context<Self>,
13835 ) {
13836 if self.take_rename(true, window, cx).is_some() {
13837 return;
13838 }
13839
13840 if self
13841 .context_menu
13842 .borrow_mut()
13843 .as_mut()
13844 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13845 .unwrap_or(false)
13846 {
13847 return;
13848 }
13849
13850 if matches!(self.mode, EditorMode::SingleLine) {
13851 cx.propagate();
13852 return;
13853 }
13854
13855 let Some(row_count) = self.visible_row_count() else {
13856 return;
13857 };
13858
13859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13860
13861 let effects = if action.center_cursor {
13862 SelectionEffects::scroll(Autoscroll::center())
13863 } else {
13864 SelectionEffects::default()
13865 };
13866
13867 let text_layout_details = &self.text_layout_details(window);
13868
13869 self.change_selections(effects, window, cx, |s| {
13870 s.move_with(|map, selection| {
13871 if !selection.is_empty() {
13872 selection.goal = SelectionGoal::None;
13873 }
13874 let (cursor, goal) = movement::up_by_rows(
13875 map,
13876 selection.end,
13877 row_count,
13878 selection.goal,
13879 false,
13880 text_layout_details,
13881 );
13882 selection.collapse_to(cursor, goal);
13883 });
13884 });
13885 }
13886
13887 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13889 let text_layout_details = &self.text_layout_details(window);
13890 self.change_selections(Default::default(), window, cx, |s| {
13891 s.move_heads_with(|map, head, goal| {
13892 movement::up(map, head, goal, false, text_layout_details)
13893 })
13894 })
13895 }
13896
13897 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13898 self.take_rename(true, window, cx);
13899
13900 if self.mode.is_single_line() {
13901 cx.propagate();
13902 return;
13903 }
13904
13905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13906
13907 let text_layout_details = &self.text_layout_details(window);
13908 let selection_count = self.selections.count();
13909 let first_selection = self.selections.first_anchor();
13910
13911 self.change_selections(Default::default(), window, cx, |s| {
13912 s.move_with(|map, selection| {
13913 if !selection.is_empty() {
13914 selection.goal = SelectionGoal::None;
13915 }
13916 let (cursor, goal) = movement::down(
13917 map,
13918 selection.end,
13919 selection.goal,
13920 false,
13921 text_layout_details,
13922 );
13923 selection.collapse_to(cursor, goal);
13924 });
13925 });
13926
13927 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13928 {
13929 cx.propagate();
13930 }
13931 }
13932
13933 pub fn select_page_down(
13934 &mut self,
13935 _: &SelectPageDown,
13936 window: &mut Window,
13937 cx: &mut Context<Self>,
13938 ) {
13939 let Some(row_count) = self.visible_row_count() else {
13940 return;
13941 };
13942
13943 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13944
13945 let text_layout_details = &self.text_layout_details(window);
13946
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.move_heads_with(|map, head, goal| {
13949 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13950 })
13951 })
13952 }
13953
13954 pub fn move_page_down(
13955 &mut self,
13956 action: &MovePageDown,
13957 window: &mut Window,
13958 cx: &mut Context<Self>,
13959 ) {
13960 if self.take_rename(true, window, cx).is_some() {
13961 return;
13962 }
13963
13964 if self
13965 .context_menu
13966 .borrow_mut()
13967 .as_mut()
13968 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13969 .unwrap_or(false)
13970 {
13971 return;
13972 }
13973
13974 if matches!(self.mode, EditorMode::SingleLine) {
13975 cx.propagate();
13976 return;
13977 }
13978
13979 let Some(row_count) = self.visible_row_count() else {
13980 return;
13981 };
13982
13983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13984
13985 let effects = if action.center_cursor {
13986 SelectionEffects::scroll(Autoscroll::center())
13987 } else {
13988 SelectionEffects::default()
13989 };
13990
13991 let text_layout_details = &self.text_layout_details(window);
13992 self.change_selections(effects, window, cx, |s| {
13993 s.move_with(|map, selection| {
13994 if !selection.is_empty() {
13995 selection.goal = SelectionGoal::None;
13996 }
13997 let (cursor, goal) = movement::down_by_rows(
13998 map,
13999 selection.end,
14000 row_count,
14001 selection.goal,
14002 false,
14003 text_layout_details,
14004 );
14005 selection.collapse_to(cursor, goal);
14006 });
14007 });
14008 }
14009
14010 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14011 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14012 let text_layout_details = &self.text_layout_details(window);
14013 self.change_selections(Default::default(), window, cx, |s| {
14014 s.move_heads_with(|map, head, goal| {
14015 movement::down(map, head, goal, false, text_layout_details)
14016 })
14017 });
14018 }
14019
14020 pub fn context_menu_first(
14021 &mut self,
14022 _: &ContextMenuFirst,
14023 window: &mut Window,
14024 cx: &mut Context<Self>,
14025 ) {
14026 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14027 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14028 }
14029 }
14030
14031 pub fn context_menu_prev(
14032 &mut self,
14033 _: &ContextMenuPrevious,
14034 window: &mut Window,
14035 cx: &mut Context<Self>,
14036 ) {
14037 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14038 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14039 }
14040 }
14041
14042 pub fn context_menu_next(
14043 &mut self,
14044 _: &ContextMenuNext,
14045 window: &mut Window,
14046 cx: &mut Context<Self>,
14047 ) {
14048 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14049 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14050 }
14051 }
14052
14053 pub fn context_menu_last(
14054 &mut self,
14055 _: &ContextMenuLast,
14056 window: &mut Window,
14057 cx: &mut Context<Self>,
14058 ) {
14059 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14060 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14061 }
14062 }
14063
14064 pub fn signature_help_prev(
14065 &mut self,
14066 _: &SignatureHelpPrevious,
14067 _: &mut Window,
14068 cx: &mut Context<Self>,
14069 ) {
14070 if let Some(popover) = self.signature_help_state.popover_mut() {
14071 if popover.current_signature == 0 {
14072 popover.current_signature = popover.signatures.len() - 1;
14073 } else {
14074 popover.current_signature -= 1;
14075 }
14076 cx.notify();
14077 }
14078 }
14079
14080 pub fn signature_help_next(
14081 &mut self,
14082 _: &SignatureHelpNext,
14083 _: &mut Window,
14084 cx: &mut Context<Self>,
14085 ) {
14086 if let Some(popover) = self.signature_help_state.popover_mut() {
14087 if popover.current_signature + 1 == popover.signatures.len() {
14088 popover.current_signature = 0;
14089 } else {
14090 popover.current_signature += 1;
14091 }
14092 cx.notify();
14093 }
14094 }
14095
14096 pub fn move_to_previous_word_start(
14097 &mut self,
14098 _: &MoveToPreviousWordStart,
14099 window: &mut Window,
14100 cx: &mut Context<Self>,
14101 ) {
14102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14103 self.change_selections(Default::default(), window, cx, |s| {
14104 s.move_cursors_with(|map, head, _| {
14105 (
14106 movement::previous_word_start(map, head),
14107 SelectionGoal::None,
14108 )
14109 });
14110 })
14111 }
14112
14113 pub fn move_to_previous_subword_start(
14114 &mut self,
14115 _: &MoveToPreviousSubwordStart,
14116 window: &mut Window,
14117 cx: &mut Context<Self>,
14118 ) {
14119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14120 self.change_selections(Default::default(), window, cx, |s| {
14121 s.move_cursors_with(|map, head, _| {
14122 (
14123 movement::previous_subword_start(map, head),
14124 SelectionGoal::None,
14125 )
14126 });
14127 })
14128 }
14129
14130 pub fn select_to_previous_word_start(
14131 &mut self,
14132 _: &SelectToPreviousWordStart,
14133 window: &mut Window,
14134 cx: &mut Context<Self>,
14135 ) {
14136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14137 self.change_selections(Default::default(), window, cx, |s| {
14138 s.move_heads_with(|map, head, _| {
14139 (
14140 movement::previous_word_start(map, head),
14141 SelectionGoal::None,
14142 )
14143 });
14144 })
14145 }
14146
14147 pub fn select_to_previous_subword_start(
14148 &mut self,
14149 _: &SelectToPreviousSubwordStart,
14150 window: &mut Window,
14151 cx: &mut Context<Self>,
14152 ) {
14153 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14154 self.change_selections(Default::default(), window, cx, |s| {
14155 s.move_heads_with(|map, head, _| {
14156 (
14157 movement::previous_subword_start(map, head),
14158 SelectionGoal::None,
14159 )
14160 });
14161 })
14162 }
14163
14164 pub fn delete_to_previous_word_start(
14165 &mut self,
14166 action: &DeleteToPreviousWordStart,
14167 window: &mut Window,
14168 cx: &mut Context<Self>,
14169 ) {
14170 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14171 self.transact(window, cx, |this, window, cx| {
14172 this.select_autoclose_pair(window, cx);
14173 this.change_selections(Default::default(), window, cx, |s| {
14174 s.move_with(|map, selection| {
14175 if selection.is_empty() {
14176 let mut cursor = if action.ignore_newlines {
14177 movement::previous_word_start(map, selection.head())
14178 } else {
14179 movement::previous_word_start_or_newline(map, selection.head())
14180 };
14181 cursor = movement::adjust_greedy_deletion(
14182 map,
14183 selection.head(),
14184 cursor,
14185 action.ignore_brackets,
14186 );
14187 selection.set_head(cursor, SelectionGoal::None);
14188 }
14189 });
14190 });
14191 this.insert("", window, cx);
14192 });
14193 }
14194
14195 pub fn delete_to_previous_subword_start(
14196 &mut self,
14197 action: &DeleteToPreviousSubwordStart,
14198 window: &mut Window,
14199 cx: &mut Context<Self>,
14200 ) {
14201 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14202 self.transact(window, cx, |this, window, cx| {
14203 this.select_autoclose_pair(window, cx);
14204 this.change_selections(Default::default(), window, cx, |s| {
14205 s.move_with(|map, selection| {
14206 if selection.is_empty() {
14207 let mut cursor = if action.ignore_newlines {
14208 movement::previous_subword_start(map, selection.head())
14209 } else {
14210 movement::previous_subword_start_or_newline(map, selection.head())
14211 };
14212 cursor = movement::adjust_greedy_deletion(
14213 map,
14214 selection.head(),
14215 cursor,
14216 action.ignore_brackets,
14217 );
14218 selection.set_head(cursor, SelectionGoal::None);
14219 }
14220 });
14221 });
14222 this.insert("", window, cx);
14223 });
14224 }
14225
14226 pub fn move_to_next_word_end(
14227 &mut self,
14228 _: &MoveToNextWordEnd,
14229 window: &mut Window,
14230 cx: &mut Context<Self>,
14231 ) {
14232 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14233 self.change_selections(Default::default(), window, cx, |s| {
14234 s.move_cursors_with(|map, head, _| {
14235 (movement::next_word_end(map, head), SelectionGoal::None)
14236 });
14237 })
14238 }
14239
14240 pub fn move_to_next_subword_end(
14241 &mut self,
14242 _: &MoveToNextSubwordEnd,
14243 window: &mut Window,
14244 cx: &mut Context<Self>,
14245 ) {
14246 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14247 self.change_selections(Default::default(), window, cx, |s| {
14248 s.move_cursors_with(|map, head, _| {
14249 (movement::next_subword_end(map, head), SelectionGoal::None)
14250 });
14251 })
14252 }
14253
14254 pub fn select_to_next_word_end(
14255 &mut self,
14256 _: &SelectToNextWordEnd,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) {
14260 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14261 self.change_selections(Default::default(), window, cx, |s| {
14262 s.move_heads_with(|map, head, _| {
14263 (movement::next_word_end(map, head), SelectionGoal::None)
14264 });
14265 })
14266 }
14267
14268 pub fn select_to_next_subword_end(
14269 &mut self,
14270 _: &SelectToNextSubwordEnd,
14271 window: &mut Window,
14272 cx: &mut Context<Self>,
14273 ) {
14274 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14275 self.change_selections(Default::default(), window, cx, |s| {
14276 s.move_heads_with(|map, head, _| {
14277 (movement::next_subword_end(map, head), SelectionGoal::None)
14278 });
14279 })
14280 }
14281
14282 pub fn delete_to_next_word_end(
14283 &mut self,
14284 action: &DeleteToNextWordEnd,
14285 window: &mut Window,
14286 cx: &mut Context<Self>,
14287 ) {
14288 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14289 self.transact(window, cx, |this, window, cx| {
14290 this.change_selections(Default::default(), window, cx, |s| {
14291 s.move_with(|map, selection| {
14292 if selection.is_empty() {
14293 let mut cursor = if action.ignore_newlines {
14294 movement::next_word_end(map, selection.head())
14295 } else {
14296 movement::next_word_end_or_newline(map, selection.head())
14297 };
14298 cursor = movement::adjust_greedy_deletion(
14299 map,
14300 selection.head(),
14301 cursor,
14302 action.ignore_brackets,
14303 );
14304 selection.set_head(cursor, SelectionGoal::None);
14305 }
14306 });
14307 });
14308 this.insert("", window, cx);
14309 });
14310 }
14311
14312 pub fn delete_to_next_subword_end(
14313 &mut self,
14314 action: &DeleteToNextSubwordEnd,
14315 window: &mut Window,
14316 cx: &mut Context<Self>,
14317 ) {
14318 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14319 self.transact(window, cx, |this, window, cx| {
14320 this.change_selections(Default::default(), window, cx, |s| {
14321 s.move_with(|map, selection| {
14322 if selection.is_empty() {
14323 let mut cursor = if action.ignore_newlines {
14324 movement::next_subword_end(map, selection.head())
14325 } else {
14326 movement::next_subword_end_or_newline(map, selection.head())
14327 };
14328 cursor = movement::adjust_greedy_deletion(
14329 map,
14330 selection.head(),
14331 cursor,
14332 action.ignore_brackets,
14333 );
14334 selection.set_head(cursor, SelectionGoal::None);
14335 }
14336 });
14337 });
14338 this.insert("", window, cx);
14339 });
14340 }
14341
14342 pub fn move_to_beginning_of_line(
14343 &mut self,
14344 action: &MoveToBeginningOfLine,
14345 window: &mut Window,
14346 cx: &mut Context<Self>,
14347 ) {
14348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14349 self.change_selections(Default::default(), window, cx, |s| {
14350 s.move_cursors_with(|map, head, _| {
14351 (
14352 movement::indented_line_beginning(
14353 map,
14354 head,
14355 action.stop_at_soft_wraps,
14356 action.stop_at_indent,
14357 ),
14358 SelectionGoal::None,
14359 )
14360 });
14361 })
14362 }
14363
14364 pub fn select_to_beginning_of_line(
14365 &mut self,
14366 action: &SelectToBeginningOfLine,
14367 window: &mut Window,
14368 cx: &mut Context<Self>,
14369 ) {
14370 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14371 self.change_selections(Default::default(), window, cx, |s| {
14372 s.move_heads_with(|map, head, _| {
14373 (
14374 movement::indented_line_beginning(
14375 map,
14376 head,
14377 action.stop_at_soft_wraps,
14378 action.stop_at_indent,
14379 ),
14380 SelectionGoal::None,
14381 )
14382 });
14383 });
14384 }
14385
14386 pub fn delete_to_beginning_of_line(
14387 &mut self,
14388 action: &DeleteToBeginningOfLine,
14389 window: &mut Window,
14390 cx: &mut Context<Self>,
14391 ) {
14392 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14393 self.transact(window, cx, |this, window, cx| {
14394 this.change_selections(Default::default(), window, cx, |s| {
14395 s.move_with(|_, selection| {
14396 selection.reversed = true;
14397 });
14398 });
14399
14400 this.select_to_beginning_of_line(
14401 &SelectToBeginningOfLine {
14402 stop_at_soft_wraps: false,
14403 stop_at_indent: action.stop_at_indent,
14404 },
14405 window,
14406 cx,
14407 );
14408 this.backspace(&Backspace, window, cx);
14409 });
14410 }
14411
14412 pub fn move_to_end_of_line(
14413 &mut self,
14414 action: &MoveToEndOfLine,
14415 window: &mut Window,
14416 cx: &mut Context<Self>,
14417 ) {
14418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14419 self.change_selections(Default::default(), window, cx, |s| {
14420 s.move_cursors_with(|map, head, _| {
14421 (
14422 movement::line_end(map, head, action.stop_at_soft_wraps),
14423 SelectionGoal::None,
14424 )
14425 });
14426 })
14427 }
14428
14429 pub fn select_to_end_of_line(
14430 &mut self,
14431 action: &SelectToEndOfLine,
14432 window: &mut Window,
14433 cx: &mut Context<Self>,
14434 ) {
14435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14436 self.change_selections(Default::default(), window, cx, |s| {
14437 s.move_heads_with(|map, head, _| {
14438 (
14439 movement::line_end(map, head, action.stop_at_soft_wraps),
14440 SelectionGoal::None,
14441 )
14442 });
14443 })
14444 }
14445
14446 pub fn delete_to_end_of_line(
14447 &mut self,
14448 _: &DeleteToEndOfLine,
14449 window: &mut Window,
14450 cx: &mut Context<Self>,
14451 ) {
14452 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14453 self.transact(window, cx, |this, window, cx| {
14454 this.select_to_end_of_line(
14455 &SelectToEndOfLine {
14456 stop_at_soft_wraps: false,
14457 },
14458 window,
14459 cx,
14460 );
14461 this.delete(&Delete, window, cx);
14462 });
14463 }
14464
14465 pub fn cut_to_end_of_line(
14466 &mut self,
14467 action: &CutToEndOfLine,
14468 window: &mut Window,
14469 cx: &mut Context<Self>,
14470 ) {
14471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14472 self.transact(window, cx, |this, window, cx| {
14473 this.select_to_end_of_line(
14474 &SelectToEndOfLine {
14475 stop_at_soft_wraps: false,
14476 },
14477 window,
14478 cx,
14479 );
14480 if !action.stop_at_newlines {
14481 this.change_selections(Default::default(), window, cx, |s| {
14482 s.move_with(|_, sel| {
14483 if sel.is_empty() {
14484 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14485 }
14486 });
14487 });
14488 }
14489 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14490 let item = this.cut_common(false, window, cx);
14491 cx.write_to_clipboard(item);
14492 });
14493 }
14494
14495 pub fn move_to_start_of_paragraph(
14496 &mut self,
14497 _: &MoveToStartOfParagraph,
14498 window: &mut Window,
14499 cx: &mut Context<Self>,
14500 ) {
14501 if matches!(self.mode, EditorMode::SingleLine) {
14502 cx.propagate();
14503 return;
14504 }
14505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14506 self.change_selections(Default::default(), window, cx, |s| {
14507 s.move_with(|map, selection| {
14508 selection.collapse_to(
14509 movement::start_of_paragraph(map, selection.head(), 1),
14510 SelectionGoal::None,
14511 )
14512 });
14513 })
14514 }
14515
14516 pub fn move_to_end_of_paragraph(
14517 &mut self,
14518 _: &MoveToEndOfParagraph,
14519 window: &mut Window,
14520 cx: &mut Context<Self>,
14521 ) {
14522 if matches!(self.mode, EditorMode::SingleLine) {
14523 cx.propagate();
14524 return;
14525 }
14526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14527 self.change_selections(Default::default(), window, cx, |s| {
14528 s.move_with(|map, selection| {
14529 selection.collapse_to(
14530 movement::end_of_paragraph(map, selection.head(), 1),
14531 SelectionGoal::None,
14532 )
14533 });
14534 })
14535 }
14536
14537 pub fn select_to_start_of_paragraph(
14538 &mut self,
14539 _: &SelectToStartOfParagraph,
14540 window: &mut Window,
14541 cx: &mut Context<Self>,
14542 ) {
14543 if matches!(self.mode, EditorMode::SingleLine) {
14544 cx.propagate();
14545 return;
14546 }
14547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14548 self.change_selections(Default::default(), window, cx, |s| {
14549 s.move_heads_with(|map, head, _| {
14550 (
14551 movement::start_of_paragraph(map, head, 1),
14552 SelectionGoal::None,
14553 )
14554 });
14555 })
14556 }
14557
14558 pub fn select_to_end_of_paragraph(
14559 &mut self,
14560 _: &SelectToEndOfParagraph,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
14564 if matches!(self.mode, EditorMode::SingleLine) {
14565 cx.propagate();
14566 return;
14567 }
14568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14569 self.change_selections(Default::default(), window, cx, |s| {
14570 s.move_heads_with(|map, head, _| {
14571 (
14572 movement::end_of_paragraph(map, head, 1),
14573 SelectionGoal::None,
14574 )
14575 });
14576 })
14577 }
14578
14579 pub fn move_to_start_of_excerpt(
14580 &mut self,
14581 _: &MoveToStartOfExcerpt,
14582 window: &mut Window,
14583 cx: &mut Context<Self>,
14584 ) {
14585 if matches!(self.mode, EditorMode::SingleLine) {
14586 cx.propagate();
14587 return;
14588 }
14589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14590 self.change_selections(Default::default(), window, cx, |s| {
14591 s.move_with(|map, selection| {
14592 selection.collapse_to(
14593 movement::start_of_excerpt(
14594 map,
14595 selection.head(),
14596 workspace::searchable::Direction::Prev,
14597 ),
14598 SelectionGoal::None,
14599 )
14600 });
14601 })
14602 }
14603
14604 pub fn move_to_start_of_next_excerpt(
14605 &mut self,
14606 _: &MoveToStartOfNextExcerpt,
14607 window: &mut Window,
14608 cx: &mut Context<Self>,
14609 ) {
14610 if matches!(self.mode, EditorMode::SingleLine) {
14611 cx.propagate();
14612 return;
14613 }
14614
14615 self.change_selections(Default::default(), window, cx, |s| {
14616 s.move_with(|map, selection| {
14617 selection.collapse_to(
14618 movement::start_of_excerpt(
14619 map,
14620 selection.head(),
14621 workspace::searchable::Direction::Next,
14622 ),
14623 SelectionGoal::None,
14624 )
14625 });
14626 })
14627 }
14628
14629 pub fn move_to_end_of_excerpt(
14630 &mut self,
14631 _: &MoveToEndOfExcerpt,
14632 window: &mut Window,
14633 cx: &mut Context<Self>,
14634 ) {
14635 if matches!(self.mode, EditorMode::SingleLine) {
14636 cx.propagate();
14637 return;
14638 }
14639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14640 self.change_selections(Default::default(), window, cx, |s| {
14641 s.move_with(|map, selection| {
14642 selection.collapse_to(
14643 movement::end_of_excerpt(
14644 map,
14645 selection.head(),
14646 workspace::searchable::Direction::Next,
14647 ),
14648 SelectionGoal::None,
14649 )
14650 });
14651 })
14652 }
14653
14654 pub fn move_to_end_of_previous_excerpt(
14655 &mut self,
14656 _: &MoveToEndOfPreviousExcerpt,
14657 window: &mut Window,
14658 cx: &mut Context<Self>,
14659 ) {
14660 if matches!(self.mode, EditorMode::SingleLine) {
14661 cx.propagate();
14662 return;
14663 }
14664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14665 self.change_selections(Default::default(), window, cx, |s| {
14666 s.move_with(|map, selection| {
14667 selection.collapse_to(
14668 movement::end_of_excerpt(
14669 map,
14670 selection.head(),
14671 workspace::searchable::Direction::Prev,
14672 ),
14673 SelectionGoal::None,
14674 )
14675 });
14676 })
14677 }
14678
14679 pub fn select_to_start_of_excerpt(
14680 &mut self,
14681 _: &SelectToStartOfExcerpt,
14682 window: &mut Window,
14683 cx: &mut Context<Self>,
14684 ) {
14685 if matches!(self.mode, EditorMode::SingleLine) {
14686 cx.propagate();
14687 return;
14688 }
14689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14690 self.change_selections(Default::default(), window, cx, |s| {
14691 s.move_heads_with(|map, head, _| {
14692 (
14693 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14694 SelectionGoal::None,
14695 )
14696 });
14697 })
14698 }
14699
14700 pub fn select_to_start_of_next_excerpt(
14701 &mut self,
14702 _: &SelectToStartOfNextExcerpt,
14703 window: &mut Window,
14704 cx: &mut Context<Self>,
14705 ) {
14706 if matches!(self.mode, EditorMode::SingleLine) {
14707 cx.propagate();
14708 return;
14709 }
14710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14711 self.change_selections(Default::default(), window, cx, |s| {
14712 s.move_heads_with(|map, head, _| {
14713 (
14714 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14715 SelectionGoal::None,
14716 )
14717 });
14718 })
14719 }
14720
14721 pub fn select_to_end_of_excerpt(
14722 &mut self,
14723 _: &SelectToEndOfExcerpt,
14724 window: &mut Window,
14725 cx: &mut Context<Self>,
14726 ) {
14727 if matches!(self.mode, EditorMode::SingleLine) {
14728 cx.propagate();
14729 return;
14730 }
14731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14732 self.change_selections(Default::default(), window, cx, |s| {
14733 s.move_heads_with(|map, head, _| {
14734 (
14735 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14736 SelectionGoal::None,
14737 )
14738 });
14739 })
14740 }
14741
14742 pub fn select_to_end_of_previous_excerpt(
14743 &mut self,
14744 _: &SelectToEndOfPreviousExcerpt,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) {
14748 if matches!(self.mode, EditorMode::SingleLine) {
14749 cx.propagate();
14750 return;
14751 }
14752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14753 self.change_selections(Default::default(), window, cx, |s| {
14754 s.move_heads_with(|map, head, _| {
14755 (
14756 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14757 SelectionGoal::None,
14758 )
14759 });
14760 })
14761 }
14762
14763 pub fn move_to_beginning(
14764 &mut self,
14765 _: &MoveToBeginning,
14766 window: &mut Window,
14767 cx: &mut Context<Self>,
14768 ) {
14769 if matches!(self.mode, EditorMode::SingleLine) {
14770 cx.propagate();
14771 return;
14772 }
14773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14774 self.change_selections(Default::default(), window, cx, |s| {
14775 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14776 });
14777 }
14778
14779 pub fn select_to_beginning(
14780 &mut self,
14781 _: &SelectToBeginning,
14782 window: &mut Window,
14783 cx: &mut Context<Self>,
14784 ) {
14785 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14786 selection.set_head(Point::zero(), SelectionGoal::None);
14787 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14788 self.change_selections(Default::default(), window, cx, |s| {
14789 s.select(vec![selection]);
14790 });
14791 }
14792
14793 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14794 if matches!(self.mode, EditorMode::SingleLine) {
14795 cx.propagate();
14796 return;
14797 }
14798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14799 let cursor = self.buffer.read(cx).read(cx).len();
14800 self.change_selections(Default::default(), window, cx, |s| {
14801 s.select_ranges(vec![cursor..cursor])
14802 });
14803 }
14804
14805 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14806 self.nav_history = nav_history;
14807 }
14808
14809 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14810 self.nav_history.as_ref()
14811 }
14812
14813 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14814 self.push_to_nav_history(
14815 self.selections.newest_anchor().head(),
14816 None,
14817 false,
14818 true,
14819 cx,
14820 );
14821 }
14822
14823 fn push_to_nav_history(
14824 &mut self,
14825 cursor_anchor: Anchor,
14826 new_position: Option<Point>,
14827 is_deactivate: bool,
14828 always: bool,
14829 cx: &mut Context<Self>,
14830 ) {
14831 if let Some(nav_history) = self.nav_history.as_mut() {
14832 let buffer = self.buffer.read(cx).read(cx);
14833 let cursor_position = cursor_anchor.to_point(&buffer);
14834 let scroll_state = self.scroll_manager.anchor();
14835 let scroll_top_row = scroll_state.top_row(&buffer);
14836 drop(buffer);
14837
14838 if let Some(new_position) = new_position {
14839 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14840 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14841 return;
14842 }
14843 }
14844
14845 nav_history.push(
14846 Some(NavigationData {
14847 cursor_anchor,
14848 cursor_position,
14849 scroll_anchor: scroll_state,
14850 scroll_top_row,
14851 }),
14852 cx,
14853 );
14854 cx.emit(EditorEvent::PushedToNavHistory {
14855 anchor: cursor_anchor,
14856 is_deactivate,
14857 })
14858 }
14859 }
14860
14861 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14862 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14863 let buffer = self.buffer.read(cx).snapshot(cx);
14864 let mut selection = self
14865 .selections
14866 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14867 selection.set_head(buffer.len(), SelectionGoal::None);
14868 self.change_selections(Default::default(), window, cx, |s| {
14869 s.select(vec![selection]);
14870 });
14871 }
14872
14873 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14874 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14875 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14876 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14877 });
14878 }
14879
14880 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14882 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14883 let mut selections = self.selections.all::<Point>(&display_map);
14884 let max_point = display_map.buffer_snapshot().max_point();
14885 for selection in &mut selections {
14886 let rows = selection.spanned_rows(true, &display_map);
14887 selection.start = Point::new(rows.start.0, 0);
14888 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14889 selection.reversed = false;
14890 }
14891 self.change_selections(Default::default(), window, cx, |s| {
14892 s.select(selections);
14893 });
14894 }
14895
14896 pub fn split_selection_into_lines(
14897 &mut self,
14898 action: &SplitSelectionIntoLines,
14899 window: &mut Window,
14900 cx: &mut Context<Self>,
14901 ) {
14902 let selections = self
14903 .selections
14904 .all::<Point>(&self.display_snapshot(cx))
14905 .into_iter()
14906 .map(|selection| selection.start..selection.end)
14907 .collect::<Vec<_>>();
14908 self.unfold_ranges(&selections, true, true, cx);
14909
14910 let mut new_selection_ranges = Vec::new();
14911 {
14912 let buffer = self.buffer.read(cx).read(cx);
14913 for selection in selections {
14914 for row in selection.start.row..selection.end.row {
14915 let line_start = Point::new(row, 0);
14916 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14917
14918 if action.keep_selections {
14919 // Keep the selection range for each line
14920 let selection_start = if row == selection.start.row {
14921 selection.start
14922 } else {
14923 line_start
14924 };
14925 new_selection_ranges.push(selection_start..line_end);
14926 } else {
14927 // Collapse to cursor at end of line
14928 new_selection_ranges.push(line_end..line_end);
14929 }
14930 }
14931
14932 let is_multiline_selection = selection.start.row != selection.end.row;
14933 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14934 // so this action feels more ergonomic when paired with other selection operations
14935 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14936 if !should_skip_last {
14937 if action.keep_selections {
14938 if is_multiline_selection {
14939 let line_start = Point::new(selection.end.row, 0);
14940 new_selection_ranges.push(line_start..selection.end);
14941 } else {
14942 new_selection_ranges.push(selection.start..selection.end);
14943 }
14944 } else {
14945 new_selection_ranges.push(selection.end..selection.end);
14946 }
14947 }
14948 }
14949 }
14950 self.change_selections(Default::default(), window, cx, |s| {
14951 s.select_ranges(new_selection_ranges);
14952 });
14953 }
14954
14955 pub fn add_selection_above(
14956 &mut self,
14957 action: &AddSelectionAbove,
14958 window: &mut Window,
14959 cx: &mut Context<Self>,
14960 ) {
14961 self.add_selection(true, action.skip_soft_wrap, window, cx);
14962 }
14963
14964 pub fn add_selection_below(
14965 &mut self,
14966 action: &AddSelectionBelow,
14967 window: &mut Window,
14968 cx: &mut Context<Self>,
14969 ) {
14970 self.add_selection(false, action.skip_soft_wrap, window, cx);
14971 }
14972
14973 fn add_selection(
14974 &mut self,
14975 above: bool,
14976 skip_soft_wrap: bool,
14977 window: &mut Window,
14978 cx: &mut Context<Self>,
14979 ) {
14980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14981
14982 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14983 let all_selections = self.selections.all::<Point>(&display_map);
14984 let text_layout_details = self.text_layout_details(window);
14985
14986 let (mut columnar_selections, new_selections_to_columnarize) = {
14987 if let Some(state) = self.add_selections_state.as_ref() {
14988 let columnar_selection_ids: HashSet<_> = state
14989 .groups
14990 .iter()
14991 .flat_map(|group| group.stack.iter())
14992 .copied()
14993 .collect();
14994
14995 all_selections
14996 .into_iter()
14997 .partition(|s| columnar_selection_ids.contains(&s.id))
14998 } else {
14999 (Vec::new(), all_selections)
15000 }
15001 };
15002
15003 let mut state = self
15004 .add_selections_state
15005 .take()
15006 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15007
15008 for selection in new_selections_to_columnarize {
15009 let range = selection.display_range(&display_map).sorted();
15010 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15011 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15012 let positions = start_x.min(end_x)..start_x.max(end_x);
15013 let mut stack = Vec::new();
15014 for row in range.start.row().0..=range.end.row().0 {
15015 if let Some(selection) = self.selections.build_columnar_selection(
15016 &display_map,
15017 DisplayRow(row),
15018 &positions,
15019 selection.reversed,
15020 &text_layout_details,
15021 ) {
15022 stack.push(selection.id);
15023 columnar_selections.push(selection);
15024 }
15025 }
15026 if !stack.is_empty() {
15027 if above {
15028 stack.reverse();
15029 }
15030 state.groups.push(AddSelectionsGroup { above, stack });
15031 }
15032 }
15033
15034 let mut final_selections = Vec::new();
15035 let end_row = if above {
15036 DisplayRow(0)
15037 } else {
15038 display_map.max_point().row()
15039 };
15040
15041 let mut last_added_item_per_group = HashMap::default();
15042 for group in state.groups.iter_mut() {
15043 if let Some(last_id) = group.stack.last() {
15044 last_added_item_per_group.insert(*last_id, group);
15045 }
15046 }
15047
15048 for selection in columnar_selections {
15049 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15050 if above == group.above {
15051 let range = selection.display_range(&display_map).sorted();
15052 debug_assert_eq!(range.start.row(), range.end.row());
15053 let mut row = range.start.row();
15054 let positions =
15055 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15056 Pixels::from(start)..Pixels::from(end)
15057 } else {
15058 let start_x =
15059 display_map.x_for_display_point(range.start, &text_layout_details);
15060 let end_x =
15061 display_map.x_for_display_point(range.end, &text_layout_details);
15062 start_x.min(end_x)..start_x.max(end_x)
15063 };
15064
15065 let mut maybe_new_selection = None;
15066 let direction = if above { -1 } else { 1 };
15067
15068 while row != end_row {
15069 let new_buffer_row = if skip_soft_wrap {
15070 let new_row = display_map
15071 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
15072 row = new_row.row();
15073 Some(new_row.to_point(&display_map).row)
15074 } else {
15075 if above {
15076 row.0 -= 1;
15077 } else {
15078 row.0 += 1;
15079 }
15080 None
15081 };
15082
15083 let new_selection = if let Some(buffer_row) = new_buffer_row {
15084 let start_col = selection.start.column;
15085 let end_col = selection.end.column;
15086 let buffer_columns = start_col.min(end_col)..start_col.max(end_col);
15087
15088 self.selections
15089 .build_columnar_selection_from_buffer_columns(
15090 &display_map,
15091 buffer_row,
15092 &buffer_columns,
15093 selection.reversed,
15094 &text_layout_details,
15095 )
15096 } else {
15097 self.selections.build_columnar_selection(
15098 &display_map,
15099 row,
15100 &positions,
15101 selection.reversed,
15102 &text_layout_details,
15103 )
15104 };
15105
15106 if let Some(new_selection) = new_selection {
15107 maybe_new_selection = Some(new_selection);
15108 break;
15109 }
15110 }
15111
15112 if let Some(new_selection) = maybe_new_selection {
15113 group.stack.push(new_selection.id);
15114 if above {
15115 final_selections.push(new_selection);
15116 final_selections.push(selection);
15117 } else {
15118 final_selections.push(selection);
15119 final_selections.push(new_selection);
15120 }
15121 } else {
15122 final_selections.push(selection);
15123 }
15124 } else {
15125 group.stack.pop();
15126 }
15127 } else {
15128 final_selections.push(selection);
15129 }
15130 }
15131
15132 self.change_selections(Default::default(), window, cx, |s| {
15133 s.select(final_selections);
15134 });
15135
15136 let final_selection_ids: HashSet<_> = self
15137 .selections
15138 .all::<Point>(&display_map)
15139 .iter()
15140 .map(|s| s.id)
15141 .collect();
15142 state.groups.retain_mut(|group| {
15143 // selections might get merged above so we remove invalid items from stacks
15144 group.stack.retain(|id| final_selection_ids.contains(id));
15145
15146 // single selection in stack can be treated as initial state
15147 group.stack.len() > 1
15148 });
15149
15150 if !state.groups.is_empty() {
15151 self.add_selections_state = Some(state);
15152 }
15153 }
15154
15155 pub fn insert_snippet_at_selections(
15156 &mut self,
15157 action: &InsertSnippet,
15158 window: &mut Window,
15159 cx: &mut Context<Self>,
15160 ) {
15161 self.try_insert_snippet_at_selections(action, window, cx)
15162 .log_err();
15163 }
15164
15165 fn try_insert_snippet_at_selections(
15166 &mut self,
15167 action: &InsertSnippet,
15168 window: &mut Window,
15169 cx: &mut Context<Self>,
15170 ) -> Result<()> {
15171 let insertion_ranges = self
15172 .selections
15173 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15174 .into_iter()
15175 .map(|selection| selection.range())
15176 .collect_vec();
15177
15178 let snippet = if let Some(snippet_body) = &action.snippet {
15179 if action.language.is_none() && action.name.is_none() {
15180 Snippet::parse(snippet_body)?
15181 } else {
15182 bail!("`snippet` is mutually exclusive with `language` and `name`")
15183 }
15184 } else if let Some(name) = &action.name {
15185 let project = self.project().context("no project")?;
15186 let snippet_store = project.read(cx).snippets().read(cx);
15187 let snippet = snippet_store
15188 .snippets_for(action.language.clone(), cx)
15189 .into_iter()
15190 .find(|snippet| snippet.name == *name)
15191 .context("snippet not found")?;
15192 Snippet::parse(&snippet.body)?
15193 } else {
15194 // todo(andrew): open modal to select snippet
15195 bail!("`name` or `snippet` is required")
15196 };
15197
15198 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15199 }
15200
15201 fn select_match_ranges(
15202 &mut self,
15203 range: Range<MultiBufferOffset>,
15204 reversed: bool,
15205 replace_newest: bool,
15206 auto_scroll: Option<Autoscroll>,
15207 window: &mut Window,
15208 cx: &mut Context<Editor>,
15209 ) {
15210 self.unfold_ranges(
15211 std::slice::from_ref(&range),
15212 false,
15213 auto_scroll.is_some(),
15214 cx,
15215 );
15216 let effects = if let Some(scroll) = auto_scroll {
15217 SelectionEffects::scroll(scroll)
15218 } else {
15219 SelectionEffects::no_scroll()
15220 };
15221 self.change_selections(effects, window, cx, |s| {
15222 if replace_newest {
15223 s.delete(s.newest_anchor().id);
15224 }
15225 if reversed {
15226 s.insert_range(range.end..range.start);
15227 } else {
15228 s.insert_range(range);
15229 }
15230 });
15231 }
15232
15233 pub fn select_next_match_internal(
15234 &mut self,
15235 display_map: &DisplaySnapshot,
15236 replace_newest: bool,
15237 autoscroll: Option<Autoscroll>,
15238 window: &mut Window,
15239 cx: &mut Context<Self>,
15240 ) -> Result<()> {
15241 let buffer = display_map.buffer_snapshot();
15242 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15243 if let Some(mut select_next_state) = self.select_next_state.take() {
15244 let query = &select_next_state.query;
15245 if !select_next_state.done {
15246 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15247 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15248 let mut next_selected_range = None;
15249
15250 let bytes_after_last_selection =
15251 buffer.bytes_in_range(last_selection.end..buffer.len());
15252 let bytes_before_first_selection =
15253 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15254 let query_matches = query
15255 .stream_find_iter(bytes_after_last_selection)
15256 .map(|result| (last_selection.end, result))
15257 .chain(
15258 query
15259 .stream_find_iter(bytes_before_first_selection)
15260 .map(|result| (MultiBufferOffset(0), result)),
15261 );
15262
15263 for (start_offset, query_match) in query_matches {
15264 let query_match = query_match.unwrap(); // can only fail due to I/O
15265 let offset_range =
15266 start_offset + query_match.start()..start_offset + query_match.end();
15267
15268 if !select_next_state.wordwise
15269 || (!buffer.is_inside_word(offset_range.start, None)
15270 && !buffer.is_inside_word(offset_range.end, None))
15271 {
15272 let idx = selections
15273 .partition_point(|selection| selection.end <= offset_range.start);
15274 let overlaps = selections
15275 .get(idx)
15276 .map_or(false, |selection| selection.start < offset_range.end);
15277
15278 if !overlaps {
15279 next_selected_range = Some(offset_range);
15280 break;
15281 }
15282 }
15283 }
15284
15285 if let Some(next_selected_range) = next_selected_range {
15286 self.select_match_ranges(
15287 next_selected_range,
15288 last_selection.reversed,
15289 replace_newest,
15290 autoscroll,
15291 window,
15292 cx,
15293 );
15294 } else {
15295 select_next_state.done = true;
15296 }
15297 }
15298
15299 self.select_next_state = Some(select_next_state);
15300 } else {
15301 let mut only_carets = true;
15302 let mut same_text_selected = true;
15303 let mut selected_text = None;
15304
15305 let mut selections_iter = selections.iter().peekable();
15306 while let Some(selection) = selections_iter.next() {
15307 if selection.start != selection.end {
15308 only_carets = false;
15309 }
15310
15311 if same_text_selected {
15312 if selected_text.is_none() {
15313 selected_text =
15314 Some(buffer.text_for_range(selection.range()).collect::<String>());
15315 }
15316
15317 if let Some(next_selection) = selections_iter.peek() {
15318 if next_selection.len() == selection.len() {
15319 let next_selected_text = buffer
15320 .text_for_range(next_selection.range())
15321 .collect::<String>();
15322 if Some(next_selected_text) != selected_text {
15323 same_text_selected = false;
15324 selected_text = None;
15325 }
15326 } else {
15327 same_text_selected = false;
15328 selected_text = None;
15329 }
15330 }
15331 }
15332 }
15333
15334 if only_carets {
15335 for selection in &mut selections {
15336 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15337 selection.start = word_range.start;
15338 selection.end = word_range.end;
15339 selection.goal = SelectionGoal::None;
15340 selection.reversed = false;
15341 self.select_match_ranges(
15342 selection.start..selection.end,
15343 selection.reversed,
15344 replace_newest,
15345 autoscroll,
15346 window,
15347 cx,
15348 );
15349 }
15350
15351 if selections.len() == 1 {
15352 let selection = selections
15353 .last()
15354 .expect("ensured that there's only one selection");
15355 let query = buffer
15356 .text_for_range(selection.start..selection.end)
15357 .collect::<String>();
15358 let is_empty = query.is_empty();
15359 let select_state = SelectNextState {
15360 query: self.build_query(&[query], cx)?,
15361 wordwise: true,
15362 done: is_empty,
15363 };
15364 self.select_next_state = Some(select_state);
15365 } else {
15366 self.select_next_state = None;
15367 }
15368 } else if let Some(selected_text) = selected_text {
15369 self.select_next_state = Some(SelectNextState {
15370 query: self.build_query(&[selected_text], cx)?,
15371 wordwise: false,
15372 done: false,
15373 });
15374 self.select_next_match_internal(
15375 display_map,
15376 replace_newest,
15377 autoscroll,
15378 window,
15379 cx,
15380 )?;
15381 }
15382 }
15383 Ok(())
15384 }
15385
15386 pub fn select_all_matches(
15387 &mut self,
15388 _action: &SelectAllMatches,
15389 window: &mut Window,
15390 cx: &mut Context<Self>,
15391 ) -> Result<()> {
15392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15393
15394 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15395
15396 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15397 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15398 else {
15399 return Ok(());
15400 };
15401
15402 let mut new_selections = Vec::new();
15403
15404 let reversed = self
15405 .selections
15406 .oldest::<MultiBufferOffset>(&display_map)
15407 .reversed;
15408 let buffer = display_map.buffer_snapshot();
15409 let query_matches = select_next_state
15410 .query
15411 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15412
15413 for query_match in query_matches.into_iter() {
15414 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15415 let offset_range = if reversed {
15416 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15417 } else {
15418 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15419 };
15420
15421 if !select_next_state.wordwise
15422 || (!buffer.is_inside_word(offset_range.start, None)
15423 && !buffer.is_inside_word(offset_range.end, None))
15424 {
15425 new_selections.push(offset_range.start..offset_range.end);
15426 }
15427 }
15428
15429 select_next_state.done = true;
15430
15431 if new_selections.is_empty() {
15432 log::error!("bug: new_selections is empty in select_all_matches");
15433 return Ok(());
15434 }
15435
15436 self.unfold_ranges(&new_selections, false, false, cx);
15437 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15438 selections.select_ranges(new_selections)
15439 });
15440
15441 Ok(())
15442 }
15443
15444 pub fn select_next(
15445 &mut self,
15446 action: &SelectNext,
15447 window: &mut Window,
15448 cx: &mut Context<Self>,
15449 ) -> Result<()> {
15450 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15452 self.select_next_match_internal(
15453 &display_map,
15454 action.replace_newest,
15455 Some(Autoscroll::newest()),
15456 window,
15457 cx,
15458 )
15459 }
15460
15461 pub fn select_previous(
15462 &mut self,
15463 action: &SelectPrevious,
15464 window: &mut Window,
15465 cx: &mut Context<Self>,
15466 ) -> Result<()> {
15467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15469 let buffer = display_map.buffer_snapshot();
15470 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15471 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15472 let query = &select_prev_state.query;
15473 if !select_prev_state.done {
15474 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15475 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15476 let mut next_selected_range = None;
15477 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15478 let bytes_before_last_selection =
15479 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15480 let bytes_after_first_selection =
15481 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15482 let query_matches = query
15483 .stream_find_iter(bytes_before_last_selection)
15484 .map(|result| (last_selection.start, result))
15485 .chain(
15486 query
15487 .stream_find_iter(bytes_after_first_selection)
15488 .map(|result| (buffer.len(), result)),
15489 );
15490 for (end_offset, query_match) in query_matches {
15491 let query_match = query_match.unwrap(); // can only fail due to I/O
15492 let offset_range =
15493 end_offset - query_match.end()..end_offset - query_match.start();
15494
15495 if !select_prev_state.wordwise
15496 || (!buffer.is_inside_word(offset_range.start, None)
15497 && !buffer.is_inside_word(offset_range.end, None))
15498 {
15499 next_selected_range = Some(offset_range);
15500 break;
15501 }
15502 }
15503
15504 if let Some(next_selected_range) = next_selected_range {
15505 self.select_match_ranges(
15506 next_selected_range,
15507 last_selection.reversed,
15508 action.replace_newest,
15509 Some(Autoscroll::newest()),
15510 window,
15511 cx,
15512 );
15513 } else {
15514 select_prev_state.done = true;
15515 }
15516 }
15517
15518 self.select_prev_state = Some(select_prev_state);
15519 } else {
15520 let mut only_carets = true;
15521 let mut same_text_selected = true;
15522 let mut selected_text = None;
15523
15524 let mut selections_iter = selections.iter().peekable();
15525 while let Some(selection) = selections_iter.next() {
15526 if selection.start != selection.end {
15527 only_carets = false;
15528 }
15529
15530 if same_text_selected {
15531 if selected_text.is_none() {
15532 selected_text =
15533 Some(buffer.text_for_range(selection.range()).collect::<String>());
15534 }
15535
15536 if let Some(next_selection) = selections_iter.peek() {
15537 if next_selection.len() == selection.len() {
15538 let next_selected_text = buffer
15539 .text_for_range(next_selection.range())
15540 .collect::<String>();
15541 if Some(next_selected_text) != selected_text {
15542 same_text_selected = false;
15543 selected_text = None;
15544 }
15545 } else {
15546 same_text_selected = false;
15547 selected_text = None;
15548 }
15549 }
15550 }
15551 }
15552
15553 if only_carets {
15554 for selection in &mut selections {
15555 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15556 selection.start = word_range.start;
15557 selection.end = word_range.end;
15558 selection.goal = SelectionGoal::None;
15559 selection.reversed = false;
15560 self.select_match_ranges(
15561 selection.start..selection.end,
15562 selection.reversed,
15563 action.replace_newest,
15564 Some(Autoscroll::newest()),
15565 window,
15566 cx,
15567 );
15568 }
15569 if selections.len() == 1 {
15570 let selection = selections
15571 .last()
15572 .expect("ensured that there's only one selection");
15573 let query = buffer
15574 .text_for_range(selection.start..selection.end)
15575 .collect::<String>();
15576 let is_empty = query.is_empty();
15577 let select_state = SelectNextState {
15578 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15579 wordwise: true,
15580 done: is_empty,
15581 };
15582 self.select_prev_state = Some(select_state);
15583 } else {
15584 self.select_prev_state = None;
15585 }
15586 } else if let Some(selected_text) = selected_text {
15587 self.select_prev_state = Some(SelectNextState {
15588 query: self
15589 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15590 wordwise: false,
15591 done: false,
15592 });
15593 self.select_previous(action, window, cx)?;
15594 }
15595 }
15596 Ok(())
15597 }
15598
15599 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15600 /// setting the case sensitivity based on the global
15601 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15602 /// editor's settings.
15603 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15604 where
15605 I: IntoIterator<Item = P>,
15606 P: AsRef<[u8]>,
15607 {
15608 let case_sensitive = self
15609 .select_next_is_case_sensitive
15610 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15611
15612 let mut builder = AhoCorasickBuilder::new();
15613 builder.ascii_case_insensitive(!case_sensitive);
15614 builder.build(patterns)
15615 }
15616
15617 pub fn find_next_match(
15618 &mut self,
15619 _: &FindNextMatch,
15620 window: &mut Window,
15621 cx: &mut Context<Self>,
15622 ) -> Result<()> {
15623 let selections = self.selections.disjoint_anchors_arc();
15624 match selections.first() {
15625 Some(first) if selections.len() >= 2 => {
15626 self.change_selections(Default::default(), window, cx, |s| {
15627 s.select_ranges([first.range()]);
15628 });
15629 }
15630 _ => self.select_next(
15631 &SelectNext {
15632 replace_newest: true,
15633 },
15634 window,
15635 cx,
15636 )?,
15637 }
15638 Ok(())
15639 }
15640
15641 pub fn find_previous_match(
15642 &mut self,
15643 _: &FindPreviousMatch,
15644 window: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) -> Result<()> {
15647 let selections = self.selections.disjoint_anchors_arc();
15648 match selections.last() {
15649 Some(last) if selections.len() >= 2 => {
15650 self.change_selections(Default::default(), window, cx, |s| {
15651 s.select_ranges([last.range()]);
15652 });
15653 }
15654 _ => self.select_previous(
15655 &SelectPrevious {
15656 replace_newest: true,
15657 },
15658 window,
15659 cx,
15660 )?,
15661 }
15662 Ok(())
15663 }
15664
15665 pub fn toggle_comments(
15666 &mut self,
15667 action: &ToggleComments,
15668 window: &mut Window,
15669 cx: &mut Context<Self>,
15670 ) {
15671 if self.read_only(cx) {
15672 return;
15673 }
15674 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15675 let text_layout_details = &self.text_layout_details(window);
15676 self.transact(window, cx, |this, window, cx| {
15677 let mut selections = this
15678 .selections
15679 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15680 let mut edits = Vec::new();
15681 let mut selection_edit_ranges = Vec::new();
15682 let mut last_toggled_row = None;
15683 let snapshot = this.buffer.read(cx).read(cx);
15684 let empty_str: Arc<str> = Arc::default();
15685 let mut suffixes_inserted = Vec::new();
15686 let ignore_indent = action.ignore_indent;
15687
15688 fn comment_prefix_range(
15689 snapshot: &MultiBufferSnapshot,
15690 row: MultiBufferRow,
15691 comment_prefix: &str,
15692 comment_prefix_whitespace: &str,
15693 ignore_indent: bool,
15694 ) -> Range<Point> {
15695 let indent_size = if ignore_indent {
15696 0
15697 } else {
15698 snapshot.indent_size_for_line(row).len
15699 };
15700
15701 let start = Point::new(row.0, indent_size);
15702
15703 let mut line_bytes = snapshot
15704 .bytes_in_range(start..snapshot.max_point())
15705 .flatten()
15706 .copied();
15707
15708 // If this line currently begins with the line comment prefix, then record
15709 // the range containing the prefix.
15710 if line_bytes
15711 .by_ref()
15712 .take(comment_prefix.len())
15713 .eq(comment_prefix.bytes())
15714 {
15715 // Include any whitespace that matches the comment prefix.
15716 let matching_whitespace_len = line_bytes
15717 .zip(comment_prefix_whitespace.bytes())
15718 .take_while(|(a, b)| a == b)
15719 .count() as u32;
15720 let end = Point::new(
15721 start.row,
15722 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15723 );
15724 start..end
15725 } else {
15726 start..start
15727 }
15728 }
15729
15730 fn comment_suffix_range(
15731 snapshot: &MultiBufferSnapshot,
15732 row: MultiBufferRow,
15733 comment_suffix: &str,
15734 comment_suffix_has_leading_space: bool,
15735 ) -> Range<Point> {
15736 let end = Point::new(row.0, snapshot.line_len(row));
15737 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15738
15739 let mut line_end_bytes = snapshot
15740 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15741 .flatten()
15742 .copied();
15743
15744 let leading_space_len = if suffix_start_column > 0
15745 && line_end_bytes.next() == Some(b' ')
15746 && comment_suffix_has_leading_space
15747 {
15748 1
15749 } else {
15750 0
15751 };
15752
15753 // If this line currently begins with the line comment prefix, then record
15754 // the range containing the prefix.
15755 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15756 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15757 start..end
15758 } else {
15759 end..end
15760 }
15761 }
15762
15763 // TODO: Handle selections that cross excerpts
15764 for selection in &mut selections {
15765 let start_column = snapshot
15766 .indent_size_for_line(MultiBufferRow(selection.start.row))
15767 .len;
15768 let language = if let Some(language) =
15769 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15770 {
15771 language
15772 } else {
15773 continue;
15774 };
15775
15776 selection_edit_ranges.clear();
15777
15778 // If multiple selections contain a given row, avoid processing that
15779 // row more than once.
15780 let mut start_row = MultiBufferRow(selection.start.row);
15781 if last_toggled_row == Some(start_row) {
15782 start_row = start_row.next_row();
15783 }
15784 let end_row =
15785 if selection.end.row > selection.start.row && selection.end.column == 0 {
15786 MultiBufferRow(selection.end.row - 1)
15787 } else {
15788 MultiBufferRow(selection.end.row)
15789 };
15790 last_toggled_row = Some(end_row);
15791
15792 if start_row > end_row {
15793 continue;
15794 }
15795
15796 // If the language has line comments, toggle those.
15797 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15798
15799 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15800 if ignore_indent {
15801 full_comment_prefixes = full_comment_prefixes
15802 .into_iter()
15803 .map(|s| Arc::from(s.trim_end()))
15804 .collect();
15805 }
15806
15807 if !full_comment_prefixes.is_empty() {
15808 let first_prefix = full_comment_prefixes
15809 .first()
15810 .expect("prefixes is non-empty");
15811 let prefix_trimmed_lengths = full_comment_prefixes
15812 .iter()
15813 .map(|p| p.trim_end_matches(' ').len())
15814 .collect::<SmallVec<[usize; 4]>>();
15815
15816 let mut all_selection_lines_are_comments = true;
15817
15818 for row in start_row.0..=end_row.0 {
15819 let row = MultiBufferRow(row);
15820 if start_row < end_row && snapshot.is_line_blank(row) {
15821 continue;
15822 }
15823
15824 let prefix_range = full_comment_prefixes
15825 .iter()
15826 .zip(prefix_trimmed_lengths.iter().copied())
15827 .map(|(prefix, trimmed_prefix_len)| {
15828 comment_prefix_range(
15829 snapshot.deref(),
15830 row,
15831 &prefix[..trimmed_prefix_len],
15832 &prefix[trimmed_prefix_len..],
15833 ignore_indent,
15834 )
15835 })
15836 .max_by_key(|range| range.end.column - range.start.column)
15837 .expect("prefixes is non-empty");
15838
15839 if prefix_range.is_empty() {
15840 all_selection_lines_are_comments = false;
15841 }
15842
15843 selection_edit_ranges.push(prefix_range);
15844 }
15845
15846 if all_selection_lines_are_comments {
15847 edits.extend(
15848 selection_edit_ranges
15849 .iter()
15850 .cloned()
15851 .map(|range| (range, empty_str.clone())),
15852 );
15853 } else {
15854 let min_column = selection_edit_ranges
15855 .iter()
15856 .map(|range| range.start.column)
15857 .min()
15858 .unwrap_or(0);
15859 edits.extend(selection_edit_ranges.iter().map(|range| {
15860 let position = Point::new(range.start.row, min_column);
15861 (position..position, first_prefix.clone())
15862 }));
15863 }
15864 } else if let Some(BlockCommentConfig {
15865 start: full_comment_prefix,
15866 end: comment_suffix,
15867 ..
15868 }) = language.block_comment()
15869 {
15870 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15871 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15872 let prefix_range = comment_prefix_range(
15873 snapshot.deref(),
15874 start_row,
15875 comment_prefix,
15876 comment_prefix_whitespace,
15877 ignore_indent,
15878 );
15879 let suffix_range = comment_suffix_range(
15880 snapshot.deref(),
15881 end_row,
15882 comment_suffix.trim_start_matches(' '),
15883 comment_suffix.starts_with(' '),
15884 );
15885
15886 if prefix_range.is_empty() || suffix_range.is_empty() {
15887 edits.push((
15888 prefix_range.start..prefix_range.start,
15889 full_comment_prefix.clone(),
15890 ));
15891 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15892 suffixes_inserted.push((end_row, comment_suffix.len()));
15893 } else {
15894 edits.push((prefix_range, empty_str.clone()));
15895 edits.push((suffix_range, empty_str.clone()));
15896 }
15897 } else {
15898 continue;
15899 }
15900 }
15901
15902 drop(snapshot);
15903 this.buffer.update(cx, |buffer, cx| {
15904 buffer.edit(edits, None, cx);
15905 });
15906
15907 // Adjust selections so that they end before any comment suffixes that
15908 // were inserted.
15909 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15910 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15911 let snapshot = this.buffer.read(cx).read(cx);
15912 for selection in &mut selections {
15913 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15914 match row.cmp(&MultiBufferRow(selection.end.row)) {
15915 Ordering::Less => {
15916 suffixes_inserted.next();
15917 continue;
15918 }
15919 Ordering::Greater => break,
15920 Ordering::Equal => {
15921 if selection.end.column == snapshot.line_len(row) {
15922 if selection.is_empty() {
15923 selection.start.column -= suffix_len as u32;
15924 }
15925 selection.end.column -= suffix_len as u32;
15926 }
15927 break;
15928 }
15929 }
15930 }
15931 }
15932
15933 drop(snapshot);
15934 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15935
15936 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15937 let selections_on_single_row = selections.windows(2).all(|selections| {
15938 selections[0].start.row == selections[1].start.row
15939 && selections[0].end.row == selections[1].end.row
15940 && selections[0].start.row == selections[0].end.row
15941 });
15942 let selections_selecting = selections
15943 .iter()
15944 .any(|selection| selection.start != selection.end);
15945 let advance_downwards = action.advance_downwards
15946 && selections_on_single_row
15947 && !selections_selecting
15948 && !matches!(this.mode, EditorMode::SingleLine);
15949
15950 if advance_downwards {
15951 let snapshot = this.buffer.read(cx).snapshot(cx);
15952
15953 this.change_selections(Default::default(), window, cx, |s| {
15954 s.move_cursors_with(|display_snapshot, display_point, _| {
15955 let mut point = display_point.to_point(display_snapshot);
15956 point.row += 1;
15957 point = snapshot.clip_point(point, Bias::Left);
15958 let display_point = point.to_display_point(display_snapshot);
15959 let goal = SelectionGoal::HorizontalPosition(
15960 display_snapshot
15961 .x_for_display_point(display_point, text_layout_details)
15962 .into(),
15963 );
15964 (display_point, goal)
15965 })
15966 });
15967 }
15968 });
15969 }
15970
15971 pub fn select_enclosing_symbol(
15972 &mut self,
15973 _: &SelectEnclosingSymbol,
15974 window: &mut Window,
15975 cx: &mut Context<Self>,
15976 ) {
15977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15978
15979 let buffer = self.buffer.read(cx).snapshot(cx);
15980 let old_selections = self
15981 .selections
15982 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15983 .into_boxed_slice();
15984
15985 fn update_selection(
15986 selection: &Selection<MultiBufferOffset>,
15987 buffer_snap: &MultiBufferSnapshot,
15988 ) -> Option<Selection<MultiBufferOffset>> {
15989 let cursor = selection.head();
15990 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15991 for symbol in symbols.iter().rev() {
15992 let start = symbol.range.start.to_offset(buffer_snap);
15993 let end = symbol.range.end.to_offset(buffer_snap);
15994 let new_range = start..end;
15995 if start < selection.start || end > selection.end {
15996 return Some(Selection {
15997 id: selection.id,
15998 start: new_range.start,
15999 end: new_range.end,
16000 goal: SelectionGoal::None,
16001 reversed: selection.reversed,
16002 });
16003 }
16004 }
16005 None
16006 }
16007
16008 let mut selected_larger_symbol = false;
16009 let new_selections = old_selections
16010 .iter()
16011 .map(|selection| match update_selection(selection, &buffer) {
16012 Some(new_selection) => {
16013 if new_selection.range() != selection.range() {
16014 selected_larger_symbol = true;
16015 }
16016 new_selection
16017 }
16018 None => selection.clone(),
16019 })
16020 .collect::<Vec<_>>();
16021
16022 if selected_larger_symbol {
16023 self.change_selections(Default::default(), window, cx, |s| {
16024 s.select(new_selections);
16025 });
16026 }
16027 }
16028
16029 pub fn select_larger_syntax_node(
16030 &mut self,
16031 _: &SelectLargerSyntaxNode,
16032 window: &mut Window,
16033 cx: &mut Context<Self>,
16034 ) {
16035 let Some(visible_row_count) = self.visible_row_count() else {
16036 return;
16037 };
16038 let old_selections: Box<[_]> = self
16039 .selections
16040 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16041 .into();
16042 if old_selections.is_empty() {
16043 return;
16044 }
16045
16046 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16047
16048 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16049 let buffer = self.buffer.read(cx).snapshot(cx);
16050
16051 let mut selected_larger_node = false;
16052 let mut new_selections = old_selections
16053 .iter()
16054 .map(|selection| {
16055 let old_range = selection.start..selection.end;
16056
16057 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16058 // manually select word at selection
16059 if ["string_content", "inline"].contains(&node.kind()) {
16060 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16061 // ignore if word is already selected
16062 if !word_range.is_empty() && old_range != word_range {
16063 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16064 // only select word if start and end point belongs to same word
16065 if word_range == last_word_range {
16066 selected_larger_node = true;
16067 return Selection {
16068 id: selection.id,
16069 start: word_range.start,
16070 end: word_range.end,
16071 goal: SelectionGoal::None,
16072 reversed: selection.reversed,
16073 };
16074 }
16075 }
16076 }
16077 }
16078
16079 let mut new_range = old_range.clone();
16080 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16081 new_range = range;
16082 if !node.is_named() {
16083 continue;
16084 }
16085 if !display_map.intersects_fold(new_range.start)
16086 && !display_map.intersects_fold(new_range.end)
16087 {
16088 break;
16089 }
16090 }
16091
16092 selected_larger_node |= new_range != old_range;
16093 Selection {
16094 id: selection.id,
16095 start: new_range.start,
16096 end: new_range.end,
16097 goal: SelectionGoal::None,
16098 reversed: selection.reversed,
16099 }
16100 })
16101 .collect::<Vec<_>>();
16102
16103 if !selected_larger_node {
16104 return; // don't put this call in the history
16105 }
16106
16107 // scroll based on transformation done to the last selection created by the user
16108 let (last_old, last_new) = old_selections
16109 .last()
16110 .zip(new_selections.last().cloned())
16111 .expect("old_selections isn't empty");
16112
16113 // revert selection
16114 let is_selection_reversed = {
16115 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16116 new_selections.last_mut().expect("checked above").reversed =
16117 should_newest_selection_be_reversed;
16118 should_newest_selection_be_reversed
16119 };
16120
16121 if selected_larger_node {
16122 self.select_syntax_node_history.disable_clearing = true;
16123 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16124 s.select(new_selections.clone());
16125 });
16126 self.select_syntax_node_history.disable_clearing = false;
16127 }
16128
16129 let start_row = last_new.start.to_display_point(&display_map).row().0;
16130 let end_row = last_new.end.to_display_point(&display_map).row().0;
16131 let selection_height = end_row - start_row + 1;
16132 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16133
16134 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16135 let scroll_behavior = if fits_on_the_screen {
16136 self.request_autoscroll(Autoscroll::fit(), cx);
16137 SelectSyntaxNodeScrollBehavior::FitSelection
16138 } else if is_selection_reversed {
16139 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16140 SelectSyntaxNodeScrollBehavior::CursorTop
16141 } else {
16142 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16143 SelectSyntaxNodeScrollBehavior::CursorBottom
16144 };
16145
16146 self.select_syntax_node_history.push((
16147 old_selections,
16148 scroll_behavior,
16149 is_selection_reversed,
16150 ));
16151 }
16152
16153 pub fn select_smaller_syntax_node(
16154 &mut self,
16155 _: &SelectSmallerSyntaxNode,
16156 window: &mut Window,
16157 cx: &mut Context<Self>,
16158 ) {
16159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16160
16161 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16162 self.select_syntax_node_history.pop()
16163 {
16164 if let Some(selection) = selections.last_mut() {
16165 selection.reversed = is_selection_reversed;
16166 }
16167
16168 self.select_syntax_node_history.disable_clearing = true;
16169 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16170 s.select(selections.to_vec());
16171 });
16172 self.select_syntax_node_history.disable_clearing = false;
16173
16174 match scroll_behavior {
16175 SelectSyntaxNodeScrollBehavior::CursorTop => {
16176 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16177 }
16178 SelectSyntaxNodeScrollBehavior::FitSelection => {
16179 self.request_autoscroll(Autoscroll::fit(), cx);
16180 }
16181 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16182 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16183 }
16184 }
16185 }
16186 }
16187
16188 pub fn unwrap_syntax_node(
16189 &mut self,
16190 _: &UnwrapSyntaxNode,
16191 window: &mut Window,
16192 cx: &mut Context<Self>,
16193 ) {
16194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16195
16196 let buffer = self.buffer.read(cx).snapshot(cx);
16197 let selections = self
16198 .selections
16199 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16200 .into_iter()
16201 // subtracting the offset requires sorting
16202 .sorted_by_key(|i| i.start);
16203
16204 let full_edits = selections
16205 .into_iter()
16206 .filter_map(|selection| {
16207 let child = if selection.is_empty()
16208 && let Some((_, ancestor_range)) =
16209 buffer.syntax_ancestor(selection.start..selection.end)
16210 {
16211 ancestor_range
16212 } else {
16213 selection.range()
16214 };
16215
16216 let mut parent = child.clone();
16217 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16218 parent = ancestor_range;
16219 if parent.start < child.start || parent.end > child.end {
16220 break;
16221 }
16222 }
16223
16224 if parent == child {
16225 return None;
16226 }
16227 let text = buffer.text_for_range(child).collect::<String>();
16228 Some((selection.id, parent, text))
16229 })
16230 .collect::<Vec<_>>();
16231 if full_edits.is_empty() {
16232 return;
16233 }
16234
16235 self.transact(window, cx, |this, window, cx| {
16236 this.buffer.update(cx, |buffer, cx| {
16237 buffer.edit(
16238 full_edits
16239 .iter()
16240 .map(|(_, p, t)| (p.clone(), t.clone()))
16241 .collect::<Vec<_>>(),
16242 None,
16243 cx,
16244 );
16245 });
16246 this.change_selections(Default::default(), window, cx, |s| {
16247 let mut offset = 0;
16248 let mut selections = vec![];
16249 for (id, parent, text) in full_edits {
16250 let start = parent.start - offset;
16251 offset += (parent.end - parent.start) - text.len();
16252 selections.push(Selection {
16253 id,
16254 start,
16255 end: start + text.len(),
16256 reversed: false,
16257 goal: Default::default(),
16258 });
16259 }
16260 s.select(selections);
16261 });
16262 });
16263 }
16264
16265 pub fn select_next_syntax_node(
16266 &mut self,
16267 _: &SelectNextSyntaxNode,
16268 window: &mut Window,
16269 cx: &mut Context<Self>,
16270 ) {
16271 let old_selections: Box<[_]> = self
16272 .selections
16273 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16274 .into();
16275 if old_selections.is_empty() {
16276 return;
16277 }
16278
16279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16280
16281 let buffer = self.buffer.read(cx).snapshot(cx);
16282 let mut selected_sibling = false;
16283
16284 let new_selections = old_selections
16285 .iter()
16286 .map(|selection| {
16287 let old_range = selection.start..selection.end;
16288
16289 let old_range =
16290 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16291 let excerpt = buffer.excerpt_containing(old_range.clone());
16292
16293 if let Some(mut excerpt) = excerpt
16294 && let Some(node) = excerpt
16295 .buffer()
16296 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16297 {
16298 let new_range = excerpt.map_range_from_buffer(
16299 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16300 );
16301 selected_sibling = true;
16302 Selection {
16303 id: selection.id,
16304 start: new_range.start,
16305 end: new_range.end,
16306 goal: SelectionGoal::None,
16307 reversed: selection.reversed,
16308 }
16309 } else {
16310 selection.clone()
16311 }
16312 })
16313 .collect::<Vec<_>>();
16314
16315 if selected_sibling {
16316 self.change_selections(
16317 SelectionEffects::scroll(Autoscroll::fit()),
16318 window,
16319 cx,
16320 |s| {
16321 s.select(new_selections);
16322 },
16323 );
16324 }
16325 }
16326
16327 pub fn select_prev_syntax_node(
16328 &mut self,
16329 _: &SelectPreviousSyntaxNode,
16330 window: &mut Window,
16331 cx: &mut Context<Self>,
16332 ) {
16333 let old_selections: Box<[_]> = self
16334 .selections
16335 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16336 .into();
16337 if old_selections.is_empty() {
16338 return;
16339 }
16340
16341 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16342
16343 let buffer = self.buffer.read(cx).snapshot(cx);
16344 let mut selected_sibling = false;
16345
16346 let new_selections = old_selections
16347 .iter()
16348 .map(|selection| {
16349 let old_range = selection.start..selection.end;
16350 let old_range =
16351 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16352 let excerpt = buffer.excerpt_containing(old_range.clone());
16353
16354 if let Some(mut excerpt) = excerpt
16355 && let Some(node) = excerpt
16356 .buffer()
16357 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16358 {
16359 let new_range = excerpt.map_range_from_buffer(
16360 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16361 );
16362 selected_sibling = true;
16363 Selection {
16364 id: selection.id,
16365 start: new_range.start,
16366 end: new_range.end,
16367 goal: SelectionGoal::None,
16368 reversed: selection.reversed,
16369 }
16370 } else {
16371 selection.clone()
16372 }
16373 })
16374 .collect::<Vec<_>>();
16375
16376 if selected_sibling {
16377 self.change_selections(
16378 SelectionEffects::scroll(Autoscroll::fit()),
16379 window,
16380 cx,
16381 |s| {
16382 s.select(new_selections);
16383 },
16384 );
16385 }
16386 }
16387
16388 pub fn move_to_start_of_larger_syntax_node(
16389 &mut self,
16390 _: &MoveToStartOfLargerSyntaxNode,
16391 window: &mut Window,
16392 cx: &mut Context<Self>,
16393 ) {
16394 self.move_cursors_to_syntax_nodes(window, cx, false);
16395 }
16396
16397 pub fn move_to_end_of_larger_syntax_node(
16398 &mut self,
16399 _: &MoveToEndOfLargerSyntaxNode,
16400 window: &mut Window,
16401 cx: &mut Context<Self>,
16402 ) {
16403 self.move_cursors_to_syntax_nodes(window, cx, true);
16404 }
16405
16406 fn move_cursors_to_syntax_nodes(
16407 &mut self,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 move_to_end: bool,
16411 ) -> bool {
16412 let old_selections: Box<[_]> = self
16413 .selections
16414 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16415 .into();
16416 if old_selections.is_empty() {
16417 return false;
16418 }
16419
16420 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16421
16422 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16423 let buffer = self.buffer.read(cx).snapshot(cx);
16424
16425 let mut any_cursor_moved = false;
16426 let new_selections = old_selections
16427 .iter()
16428 .map(|selection| {
16429 if !selection.is_empty() {
16430 return selection.clone();
16431 }
16432
16433 let selection_pos = selection.head();
16434 let old_range = selection_pos..selection_pos;
16435
16436 let mut new_pos = selection_pos;
16437 let mut search_range = old_range;
16438 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16439 search_range = range.clone();
16440 if !node.is_named()
16441 || display_map.intersects_fold(range.start)
16442 || display_map.intersects_fold(range.end)
16443 // If cursor is already at the end of the syntax node, continue searching
16444 || (move_to_end && range.end == selection_pos)
16445 // If cursor is already at the start of the syntax node, continue searching
16446 || (!move_to_end && range.start == selection_pos)
16447 {
16448 continue;
16449 }
16450
16451 // If we found a string_content node, find the largest parent that is still string_content
16452 // Enables us to skip to the end of strings without taking multiple steps inside the string
16453 let (_, final_range) = if node.kind() == "string_content" {
16454 let mut current_node = node;
16455 let mut current_range = range;
16456 while let Some((parent, parent_range)) =
16457 buffer.syntax_ancestor(current_range.clone())
16458 {
16459 if parent.kind() == "string_content" {
16460 current_node = parent;
16461 current_range = parent_range;
16462 } else {
16463 break;
16464 }
16465 }
16466
16467 (current_node, current_range)
16468 } else {
16469 (node, range)
16470 };
16471
16472 new_pos = if move_to_end {
16473 final_range.end
16474 } else {
16475 final_range.start
16476 };
16477
16478 break;
16479 }
16480
16481 any_cursor_moved |= new_pos != selection_pos;
16482
16483 Selection {
16484 id: selection.id,
16485 start: new_pos,
16486 end: new_pos,
16487 goal: SelectionGoal::None,
16488 reversed: false,
16489 }
16490 })
16491 .collect::<Vec<_>>();
16492
16493 self.change_selections(Default::default(), window, cx, |s| {
16494 s.select(new_selections);
16495 });
16496 self.request_autoscroll(Autoscroll::newest(), cx);
16497
16498 any_cursor_moved
16499 }
16500
16501 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16502 if !EditorSettings::get_global(cx).gutter.runnables {
16503 self.clear_tasks();
16504 return Task::ready(());
16505 }
16506 let project = self.project().map(Entity::downgrade);
16507 let task_sources = self.lsp_task_sources(cx);
16508 let multi_buffer = self.buffer.downgrade();
16509 cx.spawn_in(window, async move |editor, cx| {
16510 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16511 let Some(project) = project.and_then(|p| p.upgrade()) else {
16512 return;
16513 };
16514 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16515 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16516 }) else {
16517 return;
16518 };
16519
16520 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16521 if hide_runnables {
16522 return;
16523 }
16524 let new_rows =
16525 cx.background_spawn({
16526 let snapshot = display_snapshot.clone();
16527 async move {
16528 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16529 }
16530 })
16531 .await;
16532 let Ok(lsp_tasks) =
16533 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16534 else {
16535 return;
16536 };
16537 let lsp_tasks = lsp_tasks.await;
16538
16539 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16540 lsp_tasks
16541 .into_iter()
16542 .flat_map(|(kind, tasks)| {
16543 tasks.into_iter().filter_map(move |(location, task)| {
16544 Some((kind.clone(), location?, task))
16545 })
16546 })
16547 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16548 let buffer = location.target.buffer;
16549 let buffer_snapshot = buffer.read(cx).snapshot();
16550 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16551 |(excerpt_id, snapshot, _)| {
16552 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16553 display_snapshot
16554 .buffer_snapshot()
16555 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16556 } else {
16557 None
16558 }
16559 },
16560 );
16561 if let Some(offset) = offset {
16562 let task_buffer_range =
16563 location.target.range.to_point(&buffer_snapshot);
16564 let context_buffer_range =
16565 task_buffer_range.to_offset(&buffer_snapshot);
16566 let context_range = BufferOffset(context_buffer_range.start)
16567 ..BufferOffset(context_buffer_range.end);
16568
16569 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16570 .or_insert_with(|| RunnableTasks {
16571 templates: Vec::new(),
16572 offset,
16573 column: task_buffer_range.start.column,
16574 extra_variables: HashMap::default(),
16575 context_range,
16576 })
16577 .templates
16578 .push((kind, task.original_task().clone()));
16579 }
16580
16581 acc
16582 })
16583 }) else {
16584 return;
16585 };
16586
16587 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16588 buffer.language_settings(cx).tasks.prefer_lsp
16589 }) else {
16590 return;
16591 };
16592
16593 let rows = Self::runnable_rows(
16594 project,
16595 display_snapshot,
16596 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16597 new_rows,
16598 cx.clone(),
16599 )
16600 .await;
16601 editor
16602 .update(cx, |editor, _| {
16603 editor.clear_tasks();
16604 for (key, mut value) in rows {
16605 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16606 value.templates.extend(lsp_tasks.templates);
16607 }
16608
16609 editor.insert_tasks(key, value);
16610 }
16611 for (key, value) in lsp_tasks_by_rows {
16612 editor.insert_tasks(key, value);
16613 }
16614 })
16615 .ok();
16616 })
16617 }
16618 fn fetch_runnable_ranges(
16619 snapshot: &DisplaySnapshot,
16620 range: Range<Anchor>,
16621 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16622 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16623 }
16624
16625 fn runnable_rows(
16626 project: Entity<Project>,
16627 snapshot: DisplaySnapshot,
16628 prefer_lsp: bool,
16629 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16630 cx: AsyncWindowContext,
16631 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16632 cx.spawn(async move |cx| {
16633 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16634 for (run_range, mut runnable) in runnable_ranges {
16635 let Some(tasks) = cx
16636 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16637 .ok()
16638 else {
16639 continue;
16640 };
16641 let mut tasks = tasks.await;
16642
16643 if prefer_lsp {
16644 tasks.retain(|(task_kind, _)| {
16645 !matches!(task_kind, TaskSourceKind::Language { .. })
16646 });
16647 }
16648 if tasks.is_empty() {
16649 continue;
16650 }
16651
16652 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16653 let Some(row) = snapshot
16654 .buffer_snapshot()
16655 .buffer_line_for_row(MultiBufferRow(point.row))
16656 .map(|(_, range)| range.start.row)
16657 else {
16658 continue;
16659 };
16660
16661 let context_range =
16662 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16663 runnable_rows.push((
16664 (runnable.buffer_id, row),
16665 RunnableTasks {
16666 templates: tasks,
16667 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16668 context_range,
16669 column: point.column,
16670 extra_variables: runnable.extra_captures,
16671 },
16672 ));
16673 }
16674 runnable_rows
16675 })
16676 }
16677
16678 fn templates_with_tags(
16679 project: &Entity<Project>,
16680 runnable: &mut Runnable,
16681 cx: &mut App,
16682 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16683 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16684 let (worktree_id, file) = project
16685 .buffer_for_id(runnable.buffer, cx)
16686 .and_then(|buffer| buffer.read(cx).file())
16687 .map(|file| (file.worktree_id(cx), file.clone()))
16688 .unzip();
16689
16690 (
16691 project.task_store().read(cx).task_inventory().cloned(),
16692 worktree_id,
16693 file,
16694 )
16695 });
16696
16697 let tags = mem::take(&mut runnable.tags);
16698 let language = runnable.language.clone();
16699 cx.spawn(async move |cx| {
16700 let mut templates_with_tags = Vec::new();
16701 if let Some(inventory) = inventory {
16702 for RunnableTag(tag) in tags {
16703 let new_tasks = inventory.update(cx, |inventory, cx| {
16704 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16705 });
16706 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16707 move |(_, template)| {
16708 template.tags.iter().any(|source_tag| source_tag == &tag)
16709 },
16710 ));
16711 }
16712 }
16713 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16714
16715 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16716 // Strongest source wins; if we have worktree tag binding, prefer that to
16717 // global and language bindings;
16718 // if we have a global binding, prefer that to language binding.
16719 let first_mismatch = templates_with_tags
16720 .iter()
16721 .position(|(tag_source, _)| tag_source != leading_tag_source);
16722 if let Some(index) = first_mismatch {
16723 templates_with_tags.truncate(index);
16724 }
16725 }
16726
16727 templates_with_tags
16728 })
16729 }
16730
16731 pub fn move_to_enclosing_bracket(
16732 &mut self,
16733 _: &MoveToEnclosingBracket,
16734 window: &mut Window,
16735 cx: &mut Context<Self>,
16736 ) {
16737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16738 self.change_selections(Default::default(), window, cx, |s| {
16739 s.move_offsets_with(|snapshot, selection| {
16740 let Some(enclosing_bracket_ranges) =
16741 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16742 else {
16743 return;
16744 };
16745
16746 let mut best_length = usize::MAX;
16747 let mut best_inside = false;
16748 let mut best_in_bracket_range = false;
16749 let mut best_destination = None;
16750 for (open, close) in enclosing_bracket_ranges {
16751 let close = close.to_inclusive();
16752 let length = *close.end() - open.start;
16753 let inside = selection.start >= open.end && selection.end <= *close.start();
16754 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16755 || close.contains(&selection.head());
16756
16757 // If best is next to a bracket and current isn't, skip
16758 if !in_bracket_range && best_in_bracket_range {
16759 continue;
16760 }
16761
16762 // Prefer smaller lengths unless best is inside and current isn't
16763 if length > best_length && (best_inside || !inside) {
16764 continue;
16765 }
16766
16767 best_length = length;
16768 best_inside = inside;
16769 best_in_bracket_range = in_bracket_range;
16770 best_destination = Some(
16771 if close.contains(&selection.start) && close.contains(&selection.end) {
16772 if inside { open.end } else { open.start }
16773 } else if inside {
16774 *close.start()
16775 } else {
16776 *close.end()
16777 },
16778 );
16779 }
16780
16781 if let Some(destination) = best_destination {
16782 selection.collapse_to(destination, SelectionGoal::None);
16783 }
16784 })
16785 });
16786 }
16787
16788 pub fn undo_selection(
16789 &mut self,
16790 _: &UndoSelection,
16791 window: &mut Window,
16792 cx: &mut Context<Self>,
16793 ) {
16794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16795 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16796 self.selection_history.mode = SelectionHistoryMode::Undoing;
16797 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16798 this.end_selection(window, cx);
16799 this.change_selections(
16800 SelectionEffects::scroll(Autoscroll::newest()),
16801 window,
16802 cx,
16803 |s| s.select_anchors(entry.selections.to_vec()),
16804 );
16805 });
16806 self.selection_history.mode = SelectionHistoryMode::Normal;
16807
16808 self.select_next_state = entry.select_next_state;
16809 self.select_prev_state = entry.select_prev_state;
16810 self.add_selections_state = entry.add_selections_state;
16811 }
16812 }
16813
16814 pub fn redo_selection(
16815 &mut self,
16816 _: &RedoSelection,
16817 window: &mut Window,
16818 cx: &mut Context<Self>,
16819 ) {
16820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16821 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16822 self.selection_history.mode = SelectionHistoryMode::Redoing;
16823 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16824 this.end_selection(window, cx);
16825 this.change_selections(
16826 SelectionEffects::scroll(Autoscroll::newest()),
16827 window,
16828 cx,
16829 |s| s.select_anchors(entry.selections.to_vec()),
16830 );
16831 });
16832 self.selection_history.mode = SelectionHistoryMode::Normal;
16833
16834 self.select_next_state = entry.select_next_state;
16835 self.select_prev_state = entry.select_prev_state;
16836 self.add_selections_state = entry.add_selections_state;
16837 }
16838 }
16839
16840 pub fn expand_excerpts(
16841 &mut self,
16842 action: &ExpandExcerpts,
16843 _: &mut Window,
16844 cx: &mut Context<Self>,
16845 ) {
16846 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16847 }
16848
16849 pub fn expand_excerpts_down(
16850 &mut self,
16851 action: &ExpandExcerptsDown,
16852 _: &mut Window,
16853 cx: &mut Context<Self>,
16854 ) {
16855 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16856 }
16857
16858 pub fn expand_excerpts_up(
16859 &mut self,
16860 action: &ExpandExcerptsUp,
16861 _: &mut Window,
16862 cx: &mut Context<Self>,
16863 ) {
16864 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16865 }
16866
16867 pub fn expand_excerpts_for_direction(
16868 &mut self,
16869 lines: u32,
16870 direction: ExpandExcerptDirection,
16871 cx: &mut Context<Self>,
16872 ) {
16873 let selections = self.selections.disjoint_anchors_arc();
16874
16875 let lines = if lines == 0 {
16876 EditorSettings::get_global(cx).expand_excerpt_lines
16877 } else {
16878 lines
16879 };
16880
16881 let snapshot = self.buffer.read(cx).snapshot(cx);
16882 let mut excerpt_ids = selections
16883 .iter()
16884 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16885 .collect::<Vec<_>>();
16886 excerpt_ids.sort();
16887 excerpt_ids.dedup();
16888
16889 if self.delegate_expand_excerpts {
16890 cx.emit(EditorEvent::ExpandExcerptsRequested {
16891 excerpt_ids,
16892 lines,
16893 direction,
16894 });
16895 return;
16896 }
16897
16898 self.buffer.update(cx, |buffer, cx| {
16899 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16900 })
16901 }
16902
16903 pub fn expand_excerpt(
16904 &mut self,
16905 excerpt: ExcerptId,
16906 direction: ExpandExcerptDirection,
16907 window: &mut Window,
16908 cx: &mut Context<Self>,
16909 ) {
16910 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16911
16912 if self.delegate_expand_excerpts {
16913 cx.emit(EditorEvent::ExpandExcerptsRequested {
16914 excerpt_ids: vec![excerpt],
16915 lines: lines_to_expand,
16916 direction,
16917 });
16918 return;
16919 }
16920
16921 let current_scroll_position = self.scroll_position(cx);
16922 let mut scroll = None;
16923
16924 if direction == ExpandExcerptDirection::Down {
16925 let multi_buffer = self.buffer.read(cx);
16926 let snapshot = multi_buffer.snapshot(cx);
16927 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16928 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16929 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16930 {
16931 let buffer_snapshot = buffer.read(cx).snapshot();
16932 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16933 let last_row = buffer_snapshot.max_point().row;
16934 let lines_below = last_row.saturating_sub(excerpt_end_row);
16935 if lines_below >= lines_to_expand {
16936 scroll = Some(
16937 current_scroll_position
16938 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16939 );
16940 }
16941 }
16942 }
16943 if direction == ExpandExcerptDirection::Up
16944 && self
16945 .buffer
16946 .read(cx)
16947 .snapshot(cx)
16948 .excerpt_before(excerpt)
16949 .is_none()
16950 {
16951 scroll = Some(current_scroll_position);
16952 }
16953
16954 self.buffer.update(cx, |buffer, cx| {
16955 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16956 });
16957
16958 if let Some(new_scroll_position) = scroll {
16959 self.set_scroll_position(new_scroll_position, window, cx);
16960 }
16961 }
16962
16963 pub fn go_to_singleton_buffer_point(
16964 &mut self,
16965 point: Point,
16966 window: &mut Window,
16967 cx: &mut Context<Self>,
16968 ) {
16969 self.go_to_singleton_buffer_range(point..point, window, cx);
16970 }
16971
16972 pub fn go_to_singleton_buffer_range(
16973 &mut self,
16974 range: Range<Point>,
16975 window: &mut Window,
16976 cx: &mut Context<Self>,
16977 ) {
16978 let multibuffer = self.buffer().read(cx);
16979 let Some(buffer) = multibuffer.as_singleton() else {
16980 return;
16981 };
16982 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16983 return;
16984 };
16985 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16986 return;
16987 };
16988 self.change_selections(
16989 SelectionEffects::default().nav_history(true),
16990 window,
16991 cx,
16992 |s| s.select_anchor_ranges([start..end]),
16993 );
16994 }
16995
16996 pub fn go_to_diagnostic(
16997 &mut self,
16998 action: &GoToDiagnostic,
16999 window: &mut Window,
17000 cx: &mut Context<Self>,
17001 ) {
17002 if !self.diagnostics_enabled() {
17003 return;
17004 }
17005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17006 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17007 }
17008
17009 pub fn go_to_prev_diagnostic(
17010 &mut self,
17011 action: &GoToPreviousDiagnostic,
17012 window: &mut Window,
17013 cx: &mut Context<Self>,
17014 ) {
17015 if !self.diagnostics_enabled() {
17016 return;
17017 }
17018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17019 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17020 }
17021
17022 pub fn go_to_diagnostic_impl(
17023 &mut self,
17024 direction: Direction,
17025 severity: GoToDiagnosticSeverityFilter,
17026 window: &mut Window,
17027 cx: &mut Context<Self>,
17028 ) {
17029 let buffer = self.buffer.read(cx).snapshot(cx);
17030 let selection = self
17031 .selections
17032 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17033
17034 let mut active_group_id = None;
17035 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17036 && active_group.active_range.start.to_offset(&buffer) == selection.start
17037 {
17038 active_group_id = Some(active_group.group_id);
17039 }
17040
17041 fn filtered<'a>(
17042 severity: GoToDiagnosticSeverityFilter,
17043 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17044 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17045 diagnostics
17046 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17047 .filter(|entry| entry.range.start != entry.range.end)
17048 .filter(|entry| !entry.diagnostic.is_unnecessary)
17049 }
17050
17051 let before = filtered(
17052 severity,
17053 buffer
17054 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17055 .filter(|entry| entry.range.start <= selection.start),
17056 );
17057 let after = filtered(
17058 severity,
17059 buffer
17060 .diagnostics_in_range(selection.start..buffer.len())
17061 .filter(|entry| entry.range.start >= selection.start),
17062 );
17063
17064 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17065 if direction == Direction::Prev {
17066 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17067 {
17068 for diagnostic in prev_diagnostics.into_iter().rev() {
17069 if diagnostic.range.start != selection.start
17070 || active_group_id
17071 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17072 {
17073 found = Some(diagnostic);
17074 break 'outer;
17075 }
17076 }
17077 }
17078 } else {
17079 for diagnostic in after.chain(before) {
17080 if diagnostic.range.start != selection.start
17081 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17082 {
17083 found = Some(diagnostic);
17084 break;
17085 }
17086 }
17087 }
17088 let Some(next_diagnostic) = found else {
17089 return;
17090 };
17091
17092 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17093 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17094 return;
17095 };
17096 let snapshot = self.snapshot(window, cx);
17097 if snapshot.intersects_fold(next_diagnostic.range.start) {
17098 self.unfold_ranges(
17099 std::slice::from_ref(&next_diagnostic.range),
17100 true,
17101 false,
17102 cx,
17103 );
17104 }
17105 self.change_selections(Default::default(), window, cx, |s| {
17106 s.select_ranges(vec![
17107 next_diagnostic.range.start..next_diagnostic.range.start,
17108 ])
17109 });
17110 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17111 self.refresh_edit_prediction(false, true, window, cx);
17112 }
17113
17114 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17116 let snapshot = self.snapshot(window, cx);
17117 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17118 self.go_to_hunk_before_or_after_position(
17119 &snapshot,
17120 selection.head(),
17121 Direction::Next,
17122 window,
17123 cx,
17124 );
17125 }
17126
17127 pub fn go_to_hunk_before_or_after_position(
17128 &mut self,
17129 snapshot: &EditorSnapshot,
17130 position: Point,
17131 direction: Direction,
17132 window: &mut Window,
17133 cx: &mut Context<Editor>,
17134 ) {
17135 let row = if direction == Direction::Next {
17136 self.hunk_after_position(snapshot, position)
17137 .map(|hunk| hunk.row_range.start)
17138 } else {
17139 self.hunk_before_position(snapshot, position)
17140 };
17141
17142 if let Some(row) = row {
17143 let destination = Point::new(row.0, 0);
17144 let autoscroll = Autoscroll::center();
17145
17146 self.unfold_ranges(&[destination..destination], false, false, cx);
17147 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17148 s.select_ranges([destination..destination]);
17149 });
17150 }
17151 }
17152
17153 fn hunk_after_position(
17154 &mut self,
17155 snapshot: &EditorSnapshot,
17156 position: Point,
17157 ) -> Option<MultiBufferDiffHunk> {
17158 snapshot
17159 .buffer_snapshot()
17160 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17161 .find(|hunk| hunk.row_range.start.0 > position.row)
17162 .or_else(|| {
17163 snapshot
17164 .buffer_snapshot()
17165 .diff_hunks_in_range(Point::zero()..position)
17166 .find(|hunk| hunk.row_range.end.0 < position.row)
17167 })
17168 }
17169
17170 fn go_to_prev_hunk(
17171 &mut self,
17172 _: &GoToPreviousHunk,
17173 window: &mut Window,
17174 cx: &mut Context<Self>,
17175 ) {
17176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17177 let snapshot = self.snapshot(window, cx);
17178 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17179 self.go_to_hunk_before_or_after_position(
17180 &snapshot,
17181 selection.head(),
17182 Direction::Prev,
17183 window,
17184 cx,
17185 );
17186 }
17187
17188 fn hunk_before_position(
17189 &mut self,
17190 snapshot: &EditorSnapshot,
17191 position: Point,
17192 ) -> Option<MultiBufferRow> {
17193 snapshot
17194 .buffer_snapshot()
17195 .diff_hunk_before(position)
17196 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17197 }
17198
17199 fn go_to_next_change(
17200 &mut self,
17201 _: &GoToNextChange,
17202 window: &mut Window,
17203 cx: &mut Context<Self>,
17204 ) {
17205 if let Some(selections) = self
17206 .change_list
17207 .next_change(1, Direction::Next)
17208 .map(|s| s.to_vec())
17209 {
17210 self.change_selections(Default::default(), window, cx, |s| {
17211 let map = s.display_snapshot();
17212 s.select_display_ranges(selections.iter().map(|a| {
17213 let point = a.to_display_point(&map);
17214 point..point
17215 }))
17216 })
17217 }
17218 }
17219
17220 fn go_to_previous_change(
17221 &mut self,
17222 _: &GoToPreviousChange,
17223 window: &mut Window,
17224 cx: &mut Context<Self>,
17225 ) {
17226 if let Some(selections) = self
17227 .change_list
17228 .next_change(1, Direction::Prev)
17229 .map(|s| s.to_vec())
17230 {
17231 self.change_selections(Default::default(), window, cx, |s| {
17232 let map = s.display_snapshot();
17233 s.select_display_ranges(selections.iter().map(|a| {
17234 let point = a.to_display_point(&map);
17235 point..point
17236 }))
17237 })
17238 }
17239 }
17240
17241 pub fn go_to_next_document_highlight(
17242 &mut self,
17243 _: &GoToNextDocumentHighlight,
17244 window: &mut Window,
17245 cx: &mut Context<Self>,
17246 ) {
17247 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17248 }
17249
17250 pub fn go_to_prev_document_highlight(
17251 &mut self,
17252 _: &GoToPreviousDocumentHighlight,
17253 window: &mut Window,
17254 cx: &mut Context<Self>,
17255 ) {
17256 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17257 }
17258
17259 pub fn go_to_document_highlight_before_or_after_position(
17260 &mut self,
17261 direction: Direction,
17262 window: &mut Window,
17263 cx: &mut Context<Editor>,
17264 ) {
17265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17266 let snapshot = self.snapshot(window, cx);
17267 let buffer = &snapshot.buffer_snapshot();
17268 let position = self
17269 .selections
17270 .newest::<Point>(&snapshot.display_snapshot)
17271 .head();
17272 let anchor_position = buffer.anchor_after(position);
17273
17274 // Get all document highlights (both read and write)
17275 let mut all_highlights = Vec::new();
17276
17277 if let Some((_, read_highlights)) = self
17278 .background_highlights
17279 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17280 {
17281 all_highlights.extend(read_highlights.iter());
17282 }
17283
17284 if let Some((_, write_highlights)) = self
17285 .background_highlights
17286 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17287 {
17288 all_highlights.extend(write_highlights.iter());
17289 }
17290
17291 if all_highlights.is_empty() {
17292 return;
17293 }
17294
17295 // Sort highlights by position
17296 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17297
17298 let target_highlight = match direction {
17299 Direction::Next => {
17300 // Find the first highlight after the current position
17301 all_highlights
17302 .iter()
17303 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17304 }
17305 Direction::Prev => {
17306 // Find the last highlight before the current position
17307 all_highlights
17308 .iter()
17309 .rev()
17310 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17311 }
17312 };
17313
17314 if let Some(highlight) = target_highlight {
17315 let destination = highlight.start.to_point(buffer);
17316 let autoscroll = Autoscroll::center();
17317
17318 self.unfold_ranges(&[destination..destination], false, false, cx);
17319 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17320 s.select_ranges([destination..destination]);
17321 });
17322 }
17323 }
17324
17325 fn go_to_line<T: 'static>(
17326 &mut self,
17327 position: Anchor,
17328 highlight_color: Option<Hsla>,
17329 window: &mut Window,
17330 cx: &mut Context<Self>,
17331 ) {
17332 let snapshot = self.snapshot(window, cx).display_snapshot;
17333 let position = position.to_point(&snapshot.buffer_snapshot());
17334 let start = snapshot
17335 .buffer_snapshot()
17336 .clip_point(Point::new(position.row, 0), Bias::Left);
17337 let end = start + Point::new(1, 0);
17338 let start = snapshot.buffer_snapshot().anchor_before(start);
17339 let end = snapshot.buffer_snapshot().anchor_before(end);
17340
17341 self.highlight_rows::<T>(
17342 start..end,
17343 highlight_color
17344 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17345 Default::default(),
17346 cx,
17347 );
17348
17349 if self.buffer.read(cx).is_singleton() {
17350 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17351 }
17352 }
17353
17354 pub fn go_to_definition(
17355 &mut self,
17356 _: &GoToDefinition,
17357 window: &mut Window,
17358 cx: &mut Context<Self>,
17359 ) -> Task<Result<Navigated>> {
17360 let definition =
17361 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17362 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17363 cx.spawn_in(window, async move |editor, cx| {
17364 if definition.await? == Navigated::Yes {
17365 return Ok(Navigated::Yes);
17366 }
17367 match fallback_strategy {
17368 GoToDefinitionFallback::None => Ok(Navigated::No),
17369 GoToDefinitionFallback::FindAllReferences => {
17370 match editor.update_in(cx, |editor, window, cx| {
17371 editor.find_all_references(&FindAllReferences::default(), window, cx)
17372 })? {
17373 Some(references) => references.await,
17374 None => Ok(Navigated::No),
17375 }
17376 }
17377 }
17378 })
17379 }
17380
17381 pub fn go_to_declaration(
17382 &mut self,
17383 _: &GoToDeclaration,
17384 window: &mut Window,
17385 cx: &mut Context<Self>,
17386 ) -> Task<Result<Navigated>> {
17387 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17388 }
17389
17390 pub fn go_to_declaration_split(
17391 &mut self,
17392 _: &GoToDeclaration,
17393 window: &mut Window,
17394 cx: &mut Context<Self>,
17395 ) -> Task<Result<Navigated>> {
17396 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17397 }
17398
17399 pub fn go_to_implementation(
17400 &mut self,
17401 _: &GoToImplementation,
17402 window: &mut Window,
17403 cx: &mut Context<Self>,
17404 ) -> Task<Result<Navigated>> {
17405 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17406 }
17407
17408 pub fn go_to_implementation_split(
17409 &mut self,
17410 _: &GoToImplementationSplit,
17411 window: &mut Window,
17412 cx: &mut Context<Self>,
17413 ) -> Task<Result<Navigated>> {
17414 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17415 }
17416
17417 pub fn go_to_type_definition(
17418 &mut self,
17419 _: &GoToTypeDefinition,
17420 window: &mut Window,
17421 cx: &mut Context<Self>,
17422 ) -> Task<Result<Navigated>> {
17423 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17424 }
17425
17426 pub fn go_to_definition_split(
17427 &mut self,
17428 _: &GoToDefinitionSplit,
17429 window: &mut Window,
17430 cx: &mut Context<Self>,
17431 ) -> Task<Result<Navigated>> {
17432 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17433 }
17434
17435 pub fn go_to_type_definition_split(
17436 &mut self,
17437 _: &GoToTypeDefinitionSplit,
17438 window: &mut Window,
17439 cx: &mut Context<Self>,
17440 ) -> Task<Result<Navigated>> {
17441 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17442 }
17443
17444 fn go_to_definition_of_kind(
17445 &mut self,
17446 kind: GotoDefinitionKind,
17447 split: bool,
17448 window: &mut Window,
17449 cx: &mut Context<Self>,
17450 ) -> Task<Result<Navigated>> {
17451 let Some(provider) = self.semantics_provider.clone() else {
17452 return Task::ready(Ok(Navigated::No));
17453 };
17454 let head = self
17455 .selections
17456 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17457 .head();
17458 let buffer = self.buffer.read(cx);
17459 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17460 return Task::ready(Ok(Navigated::No));
17461 };
17462 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17463 return Task::ready(Ok(Navigated::No));
17464 };
17465
17466 cx.spawn_in(window, async move |editor, cx| {
17467 let Some(definitions) = definitions.await? else {
17468 return Ok(Navigated::No);
17469 };
17470 let navigated = editor
17471 .update_in(cx, |editor, window, cx| {
17472 editor.navigate_to_hover_links(
17473 Some(kind),
17474 definitions
17475 .into_iter()
17476 .filter(|location| {
17477 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17478 })
17479 .map(HoverLink::Text)
17480 .collect::<Vec<_>>(),
17481 split,
17482 window,
17483 cx,
17484 )
17485 })?
17486 .await?;
17487 anyhow::Ok(navigated)
17488 })
17489 }
17490
17491 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17492 let selection = self.selections.newest_anchor();
17493 let head = selection.head();
17494 let tail = selection.tail();
17495
17496 let Some((buffer, start_position)) =
17497 self.buffer.read(cx).text_anchor_for_position(head, cx)
17498 else {
17499 return;
17500 };
17501
17502 let end_position = if head != tail {
17503 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17504 return;
17505 };
17506 Some(pos)
17507 } else {
17508 None
17509 };
17510
17511 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17512 let url = if let Some(end_pos) = end_position {
17513 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17514 } else {
17515 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17516 };
17517
17518 if let Some(url) = url {
17519 cx.update(|window, cx| {
17520 if parse_zed_link(&url, cx).is_some() {
17521 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17522 } else {
17523 cx.open_url(&url);
17524 }
17525 })?;
17526 }
17527
17528 anyhow::Ok(())
17529 });
17530
17531 url_finder.detach();
17532 }
17533
17534 pub fn open_selected_filename(
17535 &mut self,
17536 _: &OpenSelectedFilename,
17537 window: &mut Window,
17538 cx: &mut Context<Self>,
17539 ) {
17540 let Some(workspace) = self.workspace() else {
17541 return;
17542 };
17543
17544 let position = self.selections.newest_anchor().head();
17545
17546 let Some((buffer, buffer_position)) =
17547 self.buffer.read(cx).text_anchor_for_position(position, cx)
17548 else {
17549 return;
17550 };
17551
17552 let project = self.project.clone();
17553
17554 cx.spawn_in(window, async move |_, cx| {
17555 let result = find_file(&buffer, project, buffer_position, cx).await;
17556
17557 if let Some((_, path)) = result {
17558 workspace
17559 .update_in(cx, |workspace, window, cx| {
17560 workspace.open_resolved_path(path, window, cx)
17561 })?
17562 .await?;
17563 }
17564 anyhow::Ok(())
17565 })
17566 .detach();
17567 }
17568
17569 pub(crate) fn navigate_to_hover_links(
17570 &mut self,
17571 kind: Option<GotoDefinitionKind>,
17572 definitions: Vec<HoverLink>,
17573 split: bool,
17574 window: &mut Window,
17575 cx: &mut Context<Editor>,
17576 ) -> Task<Result<Navigated>> {
17577 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17578 let mut first_url_or_file = None;
17579 let definitions: Vec<_> = definitions
17580 .into_iter()
17581 .filter_map(|def| match def {
17582 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17583 HoverLink::InlayHint(lsp_location, server_id) => {
17584 let computation =
17585 self.compute_target_location(lsp_location, server_id, window, cx);
17586 Some(cx.background_spawn(computation))
17587 }
17588 HoverLink::Url(url) => {
17589 first_url_or_file = Some(Either::Left(url));
17590 None
17591 }
17592 HoverLink::File(path) => {
17593 first_url_or_file = Some(Either::Right(path));
17594 None
17595 }
17596 })
17597 .collect();
17598
17599 let workspace = self.workspace();
17600
17601 cx.spawn_in(window, async move |editor, cx| {
17602 let locations: Vec<Location> = future::join_all(definitions)
17603 .await
17604 .into_iter()
17605 .filter_map(|location| location.transpose())
17606 .collect::<Result<_>>()
17607 .context("location tasks")?;
17608 let mut locations = cx.update(|_, cx| {
17609 locations
17610 .into_iter()
17611 .map(|location| {
17612 let buffer = location.buffer.read(cx);
17613 (location.buffer, location.range.to_point(buffer))
17614 })
17615 .into_group_map()
17616 })?;
17617 let mut num_locations = 0;
17618 for ranges in locations.values_mut() {
17619 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17620 ranges.dedup();
17621 num_locations += ranges.len();
17622 }
17623
17624 if num_locations > 1 {
17625 let tab_kind = match kind {
17626 Some(GotoDefinitionKind::Implementation) => "Implementations",
17627 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17628 Some(GotoDefinitionKind::Declaration) => "Declarations",
17629 Some(GotoDefinitionKind::Type) => "Types",
17630 };
17631 let title = editor
17632 .update_in(cx, |_, _, cx| {
17633 let target = locations
17634 .iter()
17635 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17636 .map(|(buffer, location)| {
17637 buffer
17638 .read(cx)
17639 .text_for_range(location.clone())
17640 .collect::<String>()
17641 })
17642 .filter(|text| !text.contains('\n'))
17643 .unique()
17644 .take(3)
17645 .join(", ");
17646 if target.is_empty() {
17647 tab_kind.to_owned()
17648 } else {
17649 format!("{tab_kind} for {target}")
17650 }
17651 })
17652 .context("buffer title")?;
17653
17654 let Some(workspace) = workspace else {
17655 return Ok(Navigated::No);
17656 };
17657
17658 let opened = workspace
17659 .update_in(cx, |workspace, window, cx| {
17660 let allow_preview = PreviewTabsSettings::get_global(cx)
17661 .enable_preview_multibuffer_from_code_navigation;
17662 Self::open_locations_in_multibuffer(
17663 workspace,
17664 locations,
17665 title,
17666 split,
17667 allow_preview,
17668 MultibufferSelectionMode::First,
17669 window,
17670 cx,
17671 )
17672 })
17673 .is_ok();
17674
17675 anyhow::Ok(Navigated::from_bool(opened))
17676 } else if num_locations == 0 {
17677 // If there is one url or file, open it directly
17678 match first_url_or_file {
17679 Some(Either::Left(url)) => {
17680 cx.update(|window, cx| {
17681 if parse_zed_link(&url, cx).is_some() {
17682 window
17683 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17684 } else {
17685 cx.open_url(&url);
17686 }
17687 })?;
17688 Ok(Navigated::Yes)
17689 }
17690 Some(Either::Right(path)) => {
17691 // TODO(andrew): respect preview tab settings
17692 // `enable_keep_preview_on_code_navigation` and
17693 // `enable_preview_file_from_code_navigation`
17694 let Some(workspace) = workspace else {
17695 return Ok(Navigated::No);
17696 };
17697 workspace
17698 .update_in(cx, |workspace, window, cx| {
17699 workspace.open_resolved_path(path, window, cx)
17700 })?
17701 .await?;
17702 Ok(Navigated::Yes)
17703 }
17704 None => Ok(Navigated::No),
17705 }
17706 } else {
17707 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17708 let target_range = target_ranges.first().unwrap().clone();
17709
17710 editor.update_in(cx, |editor, window, cx| {
17711 let range = target_range.to_point(target_buffer.read(cx));
17712 let range = editor.range_for_match(&range);
17713 let range = collapse_multiline_range(range);
17714
17715 if !split
17716 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17717 {
17718 editor.go_to_singleton_buffer_range(range, window, cx);
17719 } else {
17720 let Some(workspace) = workspace else {
17721 return Navigated::No;
17722 };
17723 let pane = workspace.read(cx).active_pane().clone();
17724 window.defer(cx, move |window, cx| {
17725 let target_editor: Entity<Self> =
17726 workspace.update(cx, |workspace, cx| {
17727 let pane = if split {
17728 workspace.adjacent_pane(window, cx)
17729 } else {
17730 workspace.active_pane().clone()
17731 };
17732
17733 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17734 let keep_old_preview = preview_tabs_settings
17735 .enable_keep_preview_on_code_navigation;
17736 let allow_new_preview = preview_tabs_settings
17737 .enable_preview_file_from_code_navigation;
17738
17739 workspace.open_project_item(
17740 pane,
17741 target_buffer.clone(),
17742 true,
17743 true,
17744 keep_old_preview,
17745 allow_new_preview,
17746 window,
17747 cx,
17748 )
17749 });
17750 target_editor.update(cx, |target_editor, cx| {
17751 // When selecting a definition in a different buffer, disable the nav history
17752 // to avoid creating a history entry at the previous cursor location.
17753 pane.update(cx, |pane, _| pane.disable_history());
17754 target_editor.go_to_singleton_buffer_range(range, window, cx);
17755 pane.update(cx, |pane, _| pane.enable_history());
17756 });
17757 });
17758 }
17759 Navigated::Yes
17760 })
17761 }
17762 })
17763 }
17764
17765 fn compute_target_location(
17766 &self,
17767 lsp_location: lsp::Location,
17768 server_id: LanguageServerId,
17769 window: &mut Window,
17770 cx: &mut Context<Self>,
17771 ) -> Task<anyhow::Result<Option<Location>>> {
17772 let Some(project) = self.project.clone() else {
17773 return Task::ready(Ok(None));
17774 };
17775
17776 cx.spawn_in(window, async move |editor, cx| {
17777 let location_task = editor.update(cx, |_, cx| {
17778 project.update(cx, |project, cx| {
17779 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17780 })
17781 })?;
17782 let location = Some({
17783 let target_buffer_handle = location_task.await.context("open local buffer")?;
17784 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17785 let target_start = target_buffer
17786 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17787 let target_end = target_buffer
17788 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17789 target_buffer.anchor_after(target_start)
17790 ..target_buffer.anchor_before(target_end)
17791 });
17792 Location {
17793 buffer: target_buffer_handle,
17794 range,
17795 }
17796 });
17797 Ok(location)
17798 })
17799 }
17800
17801 fn go_to_next_reference(
17802 &mut self,
17803 _: &GoToNextReference,
17804 window: &mut Window,
17805 cx: &mut Context<Self>,
17806 ) {
17807 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17808 if let Some(task) = task {
17809 task.detach();
17810 };
17811 }
17812
17813 fn go_to_prev_reference(
17814 &mut self,
17815 _: &GoToPreviousReference,
17816 window: &mut Window,
17817 cx: &mut Context<Self>,
17818 ) {
17819 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17820 if let Some(task) = task {
17821 task.detach();
17822 };
17823 }
17824
17825 pub fn go_to_reference_before_or_after_position(
17826 &mut self,
17827 direction: Direction,
17828 count: usize,
17829 window: &mut Window,
17830 cx: &mut Context<Self>,
17831 ) -> Option<Task<Result<()>>> {
17832 let selection = self.selections.newest_anchor();
17833 let head = selection.head();
17834
17835 let multi_buffer = self.buffer.read(cx);
17836
17837 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17838 let workspace = self.workspace()?;
17839 let project = workspace.read(cx).project().clone();
17840 let references =
17841 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17842 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17843 let Some(locations) = references.await? else {
17844 return Ok(());
17845 };
17846
17847 if locations.is_empty() {
17848 // totally normal - the cursor may be on something which is not
17849 // a symbol (e.g. a keyword)
17850 log::info!("no references found under cursor");
17851 return Ok(());
17852 }
17853
17854 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17855
17856 let (locations, current_location_index) =
17857 multi_buffer.update(cx, |multi_buffer, cx| {
17858 let mut locations = locations
17859 .into_iter()
17860 .filter_map(|loc| {
17861 let start = multi_buffer.buffer_anchor_to_anchor(
17862 &loc.buffer,
17863 loc.range.start,
17864 cx,
17865 )?;
17866 let end = multi_buffer.buffer_anchor_to_anchor(
17867 &loc.buffer,
17868 loc.range.end,
17869 cx,
17870 )?;
17871 Some(start..end)
17872 })
17873 .collect::<Vec<_>>();
17874
17875 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17876 // There is an O(n) implementation, but given this list will be
17877 // small (usually <100 items), the extra O(log(n)) factor isn't
17878 // worth the (surprisingly large amount of) extra complexity.
17879 locations
17880 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17881
17882 let head_offset = head.to_offset(&multi_buffer_snapshot);
17883
17884 let current_location_index = locations.iter().position(|loc| {
17885 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17886 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17887 });
17888
17889 (locations, current_location_index)
17890 });
17891
17892 let Some(current_location_index) = current_location_index else {
17893 // This indicates something has gone wrong, because we already
17894 // handle the "no references" case above
17895 log::error!(
17896 "failed to find current reference under cursor. Total references: {}",
17897 locations.len()
17898 );
17899 return Ok(());
17900 };
17901
17902 let destination_location_index = match direction {
17903 Direction::Next => (current_location_index + count) % locations.len(),
17904 Direction::Prev => {
17905 (current_location_index + locations.len() - count % locations.len())
17906 % locations.len()
17907 }
17908 };
17909
17910 // TODO(cameron): is this needed?
17911 // the thinking is to avoid "jumping to the current location" (avoid
17912 // polluting "jumplist" in vim terms)
17913 if current_location_index == destination_location_index {
17914 return Ok(());
17915 }
17916
17917 let Range { start, end } = locations[destination_location_index];
17918
17919 editor.update_in(cx, |editor, window, cx| {
17920 let effects = SelectionEffects::default();
17921
17922 editor.unfold_ranges(&[start..end], false, false, cx);
17923 editor.change_selections(effects, window, cx, |s| {
17924 s.select_ranges([start..start]);
17925 });
17926 })?;
17927
17928 Ok(())
17929 }))
17930 }
17931
17932 pub fn find_all_references(
17933 &mut self,
17934 action: &FindAllReferences,
17935 window: &mut Window,
17936 cx: &mut Context<Self>,
17937 ) -> Option<Task<Result<Navigated>>> {
17938 let always_open_multibuffer = action.always_open_multibuffer;
17939 let selection = self.selections.newest_anchor();
17940 let multi_buffer = self.buffer.read(cx);
17941 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17942 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17943 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17944 let head = selection_offset.head();
17945
17946 let head_anchor = multi_buffer_snapshot.anchor_at(
17947 head,
17948 if head < selection_offset.tail() {
17949 Bias::Right
17950 } else {
17951 Bias::Left
17952 },
17953 );
17954
17955 match self
17956 .find_all_references_task_sources
17957 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17958 {
17959 Ok(_) => {
17960 log::info!(
17961 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17962 );
17963 return None;
17964 }
17965 Err(i) => {
17966 self.find_all_references_task_sources.insert(i, head_anchor);
17967 }
17968 }
17969
17970 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17971 let workspace = self.workspace()?;
17972 let project = workspace.read(cx).project().clone();
17973 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17974 Some(cx.spawn_in(window, async move |editor, cx| {
17975 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17976 if let Ok(i) = editor
17977 .find_all_references_task_sources
17978 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17979 {
17980 editor.find_all_references_task_sources.remove(i);
17981 }
17982 });
17983
17984 let Some(locations) = references.await? else {
17985 return anyhow::Ok(Navigated::No);
17986 };
17987 let mut locations = cx.update(|_, cx| {
17988 locations
17989 .into_iter()
17990 .map(|location| {
17991 let buffer = location.buffer.read(cx);
17992 (location.buffer, location.range.to_point(buffer))
17993 })
17994 // if special-casing the single-match case, remove ranges
17995 // that intersect current selection
17996 .filter(|(location_buffer, location)| {
17997 if always_open_multibuffer || &buffer != location_buffer {
17998 return true;
17999 }
18000
18001 !location.contains_inclusive(&selection_point.range())
18002 })
18003 .into_group_map()
18004 })?;
18005 if locations.is_empty() {
18006 return anyhow::Ok(Navigated::No);
18007 }
18008 for ranges in locations.values_mut() {
18009 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18010 ranges.dedup();
18011 }
18012 let mut num_locations = 0;
18013 for ranges in locations.values_mut() {
18014 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18015 ranges.dedup();
18016 num_locations += ranges.len();
18017 }
18018
18019 if num_locations == 1 && !always_open_multibuffer {
18020 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18021 let target_range = target_ranges.first().unwrap().clone();
18022
18023 return editor.update_in(cx, |editor, window, cx| {
18024 let range = target_range.to_point(target_buffer.read(cx));
18025 let range = editor.range_for_match(&range);
18026 let range = range.start..range.start;
18027
18028 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18029 editor.go_to_singleton_buffer_range(range, window, cx);
18030 } else {
18031 let pane = workspace.read(cx).active_pane().clone();
18032 window.defer(cx, move |window, cx| {
18033 let target_editor: Entity<Self> =
18034 workspace.update(cx, |workspace, cx| {
18035 let pane = workspace.active_pane().clone();
18036
18037 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18038 let keep_old_preview = preview_tabs_settings
18039 .enable_keep_preview_on_code_navigation;
18040 let allow_new_preview = preview_tabs_settings
18041 .enable_preview_file_from_code_navigation;
18042
18043 workspace.open_project_item(
18044 pane,
18045 target_buffer.clone(),
18046 true,
18047 true,
18048 keep_old_preview,
18049 allow_new_preview,
18050 window,
18051 cx,
18052 )
18053 });
18054 target_editor.update(cx, |target_editor, cx| {
18055 // When selecting a definition in a different buffer, disable the nav history
18056 // to avoid creating a history entry at the previous cursor location.
18057 pane.update(cx, |pane, _| pane.disable_history());
18058 target_editor.go_to_singleton_buffer_range(range, window, cx);
18059 pane.update(cx, |pane, _| pane.enable_history());
18060 });
18061 });
18062 }
18063 Navigated::No
18064 });
18065 }
18066
18067 workspace.update_in(cx, |workspace, window, cx| {
18068 let target = locations
18069 .iter()
18070 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18071 .map(|(buffer, location)| {
18072 buffer
18073 .read(cx)
18074 .text_for_range(location.clone())
18075 .collect::<String>()
18076 })
18077 .filter(|text| !text.contains('\n'))
18078 .unique()
18079 .take(3)
18080 .join(", ");
18081 let title = if target.is_empty() {
18082 "References".to_owned()
18083 } else {
18084 format!("References to {target}")
18085 };
18086 let allow_preview = PreviewTabsSettings::get_global(cx)
18087 .enable_preview_multibuffer_from_code_navigation;
18088 Self::open_locations_in_multibuffer(
18089 workspace,
18090 locations,
18091 title,
18092 false,
18093 allow_preview,
18094 MultibufferSelectionMode::First,
18095 window,
18096 cx,
18097 );
18098 Navigated::Yes
18099 })
18100 }))
18101 }
18102
18103 /// Opens a multibuffer with the given project locations in it.
18104 pub fn open_locations_in_multibuffer(
18105 workspace: &mut Workspace,
18106 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18107 title: String,
18108 split: bool,
18109 allow_preview: bool,
18110 multibuffer_selection_mode: MultibufferSelectionMode,
18111 window: &mut Window,
18112 cx: &mut Context<Workspace>,
18113 ) {
18114 if locations.is_empty() {
18115 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18116 return;
18117 }
18118
18119 let capability = workspace.project().read(cx).capability();
18120 let mut ranges = <Vec<Range<Anchor>>>::new();
18121
18122 // a key to find existing multibuffer editors with the same set of locations
18123 // to prevent us from opening more and more multibuffer tabs for searches and the like
18124 let mut key = (title.clone(), vec![]);
18125 let excerpt_buffer = cx.new(|cx| {
18126 let key = &mut key.1;
18127 let mut multibuffer = MultiBuffer::new(capability);
18128 for (buffer, mut ranges_for_buffer) in locations {
18129 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18130 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18131 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18132 PathKey::for_buffer(&buffer, cx),
18133 buffer.clone(),
18134 ranges_for_buffer,
18135 multibuffer_context_lines(cx),
18136 cx,
18137 );
18138 ranges.extend(new_ranges)
18139 }
18140
18141 multibuffer.with_title(title)
18142 });
18143 let existing = workspace.active_pane().update(cx, |pane, cx| {
18144 pane.items()
18145 .filter_map(|item| item.downcast::<Editor>())
18146 .find(|editor| {
18147 editor
18148 .read(cx)
18149 .lookup_key
18150 .as_ref()
18151 .and_then(|it| {
18152 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18153 })
18154 .is_some_and(|it| *it == key)
18155 })
18156 });
18157 let was_existing = existing.is_some();
18158 let editor = existing.unwrap_or_else(|| {
18159 cx.new(|cx| {
18160 let mut editor = Editor::for_multibuffer(
18161 excerpt_buffer,
18162 Some(workspace.project().clone()),
18163 window,
18164 cx,
18165 );
18166 editor.lookup_key = Some(Box::new(key));
18167 editor
18168 })
18169 });
18170 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18171 MultibufferSelectionMode::First => {
18172 if let Some(first_range) = ranges.first() {
18173 editor.change_selections(
18174 SelectionEffects::no_scroll(),
18175 window,
18176 cx,
18177 |selections| {
18178 selections.clear_disjoint();
18179 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18180 },
18181 );
18182 }
18183 editor.highlight_background::<Self>(
18184 &ranges,
18185 |_, theme| theme.colors().editor_highlighted_line_background,
18186 cx,
18187 );
18188 }
18189 MultibufferSelectionMode::All => {
18190 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18191 selections.clear_disjoint();
18192 selections.select_anchor_ranges(ranges);
18193 });
18194 }
18195 });
18196
18197 let item = Box::new(editor);
18198
18199 let pane = if split {
18200 workspace.adjacent_pane(window, cx)
18201 } else {
18202 workspace.active_pane().clone()
18203 };
18204 let activate_pane = split;
18205
18206 let mut destination_index = None;
18207 pane.update(cx, |pane, cx| {
18208 if allow_preview && !was_existing {
18209 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18210 }
18211 if was_existing && !allow_preview {
18212 pane.unpreview_item_if_preview(item.item_id());
18213 }
18214 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18215 });
18216 }
18217
18218 pub fn rename(
18219 &mut self,
18220 _: &Rename,
18221 window: &mut Window,
18222 cx: &mut Context<Self>,
18223 ) -> Option<Task<Result<()>>> {
18224 use language::ToOffset as _;
18225
18226 let provider = self.semantics_provider.clone()?;
18227 let selection = self.selections.newest_anchor().clone();
18228 let (cursor_buffer, cursor_buffer_position) = self
18229 .buffer
18230 .read(cx)
18231 .text_anchor_for_position(selection.head(), cx)?;
18232 let (tail_buffer, cursor_buffer_position_end) = self
18233 .buffer
18234 .read(cx)
18235 .text_anchor_for_position(selection.tail(), cx)?;
18236 if tail_buffer != cursor_buffer {
18237 return None;
18238 }
18239
18240 let snapshot = cursor_buffer.read(cx).snapshot();
18241 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18242 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18243 let prepare_rename = provider
18244 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18245 .unwrap_or_else(|| Task::ready(Ok(None)));
18246 drop(snapshot);
18247
18248 Some(cx.spawn_in(window, async move |this, cx| {
18249 let rename_range = if let Some(range) = prepare_rename.await? {
18250 Some(range)
18251 } else {
18252 this.update(cx, |this, cx| {
18253 let buffer = this.buffer.read(cx).snapshot(cx);
18254 let mut buffer_highlights = this
18255 .document_highlights_for_position(selection.head(), &buffer)
18256 .filter(|highlight| {
18257 highlight.start.excerpt_id == selection.head().excerpt_id
18258 && highlight.end.excerpt_id == selection.head().excerpt_id
18259 });
18260 buffer_highlights
18261 .next()
18262 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18263 })?
18264 };
18265 if let Some(rename_range) = rename_range {
18266 this.update_in(cx, |this, window, cx| {
18267 let snapshot = cursor_buffer.read(cx).snapshot();
18268 let rename_buffer_range = rename_range.to_offset(&snapshot);
18269 let cursor_offset_in_rename_range =
18270 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18271 let cursor_offset_in_rename_range_end =
18272 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18273
18274 this.take_rename(false, window, cx);
18275 let buffer = this.buffer.read(cx).read(cx);
18276 let cursor_offset = selection.head().to_offset(&buffer);
18277 let rename_start =
18278 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18279 let rename_end = rename_start + rename_buffer_range.len();
18280 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18281 let mut old_highlight_id = None;
18282 let old_name: Arc<str> = buffer
18283 .chunks(rename_start..rename_end, true)
18284 .map(|chunk| {
18285 if old_highlight_id.is_none() {
18286 old_highlight_id = chunk.syntax_highlight_id;
18287 }
18288 chunk.text
18289 })
18290 .collect::<String>()
18291 .into();
18292
18293 drop(buffer);
18294
18295 // Position the selection in the rename editor so that it matches the current selection.
18296 this.show_local_selections = false;
18297 let rename_editor = cx.new(|cx| {
18298 let mut editor = Editor::single_line(window, cx);
18299 editor.buffer.update(cx, |buffer, cx| {
18300 buffer.edit(
18301 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18302 None,
18303 cx,
18304 )
18305 });
18306 let cursor_offset_in_rename_range =
18307 MultiBufferOffset(cursor_offset_in_rename_range);
18308 let cursor_offset_in_rename_range_end =
18309 MultiBufferOffset(cursor_offset_in_rename_range_end);
18310 let rename_selection_range = match cursor_offset_in_rename_range
18311 .cmp(&cursor_offset_in_rename_range_end)
18312 {
18313 Ordering::Equal => {
18314 editor.select_all(&SelectAll, window, cx);
18315 return editor;
18316 }
18317 Ordering::Less => {
18318 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18319 }
18320 Ordering::Greater => {
18321 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18322 }
18323 };
18324 if rename_selection_range.end.0 > old_name.len() {
18325 editor.select_all(&SelectAll, window, cx);
18326 } else {
18327 editor.change_selections(Default::default(), window, cx, |s| {
18328 s.select_ranges([rename_selection_range]);
18329 });
18330 }
18331 editor
18332 });
18333 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18334 if e == &EditorEvent::Focused {
18335 cx.emit(EditorEvent::FocusedIn)
18336 }
18337 })
18338 .detach();
18339
18340 let write_highlights =
18341 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18342 let read_highlights =
18343 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18344 let ranges = write_highlights
18345 .iter()
18346 .flat_map(|(_, ranges)| ranges.iter())
18347 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18348 .cloned()
18349 .collect();
18350
18351 this.highlight_text::<Rename>(
18352 ranges,
18353 HighlightStyle {
18354 fade_out: Some(0.6),
18355 ..Default::default()
18356 },
18357 cx,
18358 );
18359 let rename_focus_handle = rename_editor.focus_handle(cx);
18360 window.focus(&rename_focus_handle, cx);
18361 let block_id = this.insert_blocks(
18362 [BlockProperties {
18363 style: BlockStyle::Flex,
18364 placement: BlockPlacement::Below(range.start),
18365 height: Some(1),
18366 render: Arc::new({
18367 let rename_editor = rename_editor.clone();
18368 move |cx: &mut BlockContext| {
18369 let mut text_style = cx.editor_style.text.clone();
18370 if let Some(highlight_style) = old_highlight_id
18371 .and_then(|h| h.style(&cx.editor_style.syntax))
18372 {
18373 text_style = text_style.highlight(highlight_style);
18374 }
18375 div()
18376 .block_mouse_except_scroll()
18377 .pl(cx.anchor_x)
18378 .child(EditorElement::new(
18379 &rename_editor,
18380 EditorStyle {
18381 background: cx.theme().system().transparent,
18382 local_player: cx.editor_style.local_player,
18383 text: text_style,
18384 scrollbar_width: cx.editor_style.scrollbar_width,
18385 syntax: cx.editor_style.syntax.clone(),
18386 status: cx.editor_style.status.clone(),
18387 inlay_hints_style: HighlightStyle {
18388 font_weight: Some(FontWeight::BOLD),
18389 ..make_inlay_hints_style(cx.app)
18390 },
18391 edit_prediction_styles: make_suggestion_styles(
18392 cx.app,
18393 ),
18394 ..EditorStyle::default()
18395 },
18396 ))
18397 .into_any_element()
18398 }
18399 }),
18400 priority: 0,
18401 }],
18402 Some(Autoscroll::fit()),
18403 cx,
18404 )[0];
18405 this.pending_rename = Some(RenameState {
18406 range,
18407 old_name,
18408 editor: rename_editor,
18409 block_id,
18410 });
18411 })?;
18412 }
18413
18414 Ok(())
18415 }))
18416 }
18417
18418 pub fn confirm_rename(
18419 &mut self,
18420 _: &ConfirmRename,
18421 window: &mut Window,
18422 cx: &mut Context<Self>,
18423 ) -> Option<Task<Result<()>>> {
18424 let rename = self.take_rename(false, window, cx)?;
18425 let workspace = self.workspace()?.downgrade();
18426 let (buffer, start) = self
18427 .buffer
18428 .read(cx)
18429 .text_anchor_for_position(rename.range.start, cx)?;
18430 let (end_buffer, _) = self
18431 .buffer
18432 .read(cx)
18433 .text_anchor_for_position(rename.range.end, cx)?;
18434 if buffer != end_buffer {
18435 return None;
18436 }
18437
18438 let old_name = rename.old_name;
18439 let new_name = rename.editor.read(cx).text(cx);
18440
18441 let rename = self.semantics_provider.as_ref()?.perform_rename(
18442 &buffer,
18443 start,
18444 new_name.clone(),
18445 cx,
18446 )?;
18447
18448 Some(cx.spawn_in(window, async move |editor, cx| {
18449 let project_transaction = rename.await?;
18450 Self::open_project_transaction(
18451 &editor,
18452 workspace,
18453 project_transaction,
18454 format!("Rename: {} → {}", old_name, new_name),
18455 cx,
18456 )
18457 .await?;
18458
18459 editor.update(cx, |editor, cx| {
18460 editor.refresh_document_highlights(cx);
18461 })?;
18462 Ok(())
18463 }))
18464 }
18465
18466 fn take_rename(
18467 &mut self,
18468 moving_cursor: bool,
18469 window: &mut Window,
18470 cx: &mut Context<Self>,
18471 ) -> Option<RenameState> {
18472 let rename = self.pending_rename.take()?;
18473 if rename.editor.focus_handle(cx).is_focused(window) {
18474 window.focus(&self.focus_handle, cx);
18475 }
18476
18477 self.remove_blocks(
18478 [rename.block_id].into_iter().collect(),
18479 Some(Autoscroll::fit()),
18480 cx,
18481 );
18482 self.clear_highlights::<Rename>(cx);
18483 self.show_local_selections = true;
18484
18485 if moving_cursor {
18486 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18487 editor
18488 .selections
18489 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18490 .head()
18491 });
18492
18493 // Update the selection to match the position of the selection inside
18494 // the rename editor.
18495 let snapshot = self.buffer.read(cx).read(cx);
18496 let rename_range = rename.range.to_offset(&snapshot);
18497 let cursor_in_editor = snapshot
18498 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18499 .min(rename_range.end);
18500 drop(snapshot);
18501
18502 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18503 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18504 });
18505 } else {
18506 self.refresh_document_highlights(cx);
18507 }
18508
18509 Some(rename)
18510 }
18511
18512 pub fn pending_rename(&self) -> Option<&RenameState> {
18513 self.pending_rename.as_ref()
18514 }
18515
18516 fn format(
18517 &mut self,
18518 _: &Format,
18519 window: &mut Window,
18520 cx: &mut Context<Self>,
18521 ) -> Option<Task<Result<()>>> {
18522 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18523
18524 let project = match &self.project {
18525 Some(project) => project.clone(),
18526 None => return None,
18527 };
18528
18529 Some(self.perform_format(
18530 project,
18531 FormatTrigger::Manual,
18532 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18533 window,
18534 cx,
18535 ))
18536 }
18537
18538 fn format_selections(
18539 &mut self,
18540 _: &FormatSelections,
18541 window: &mut Window,
18542 cx: &mut Context<Self>,
18543 ) -> Option<Task<Result<()>>> {
18544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18545
18546 let project = match &self.project {
18547 Some(project) => project.clone(),
18548 None => return None,
18549 };
18550
18551 let ranges = self
18552 .selections
18553 .all_adjusted(&self.display_snapshot(cx))
18554 .into_iter()
18555 .map(|selection| selection.range())
18556 .collect_vec();
18557
18558 Some(self.perform_format(
18559 project,
18560 FormatTrigger::Manual,
18561 FormatTarget::Ranges(ranges),
18562 window,
18563 cx,
18564 ))
18565 }
18566
18567 fn perform_format(
18568 &mut self,
18569 project: Entity<Project>,
18570 trigger: FormatTrigger,
18571 target: FormatTarget,
18572 window: &mut Window,
18573 cx: &mut Context<Self>,
18574 ) -> Task<Result<()>> {
18575 let buffer = self.buffer.clone();
18576 let (buffers, target) = match target {
18577 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18578 FormatTarget::Ranges(selection_ranges) => {
18579 let multi_buffer = buffer.read(cx);
18580 let snapshot = multi_buffer.read(cx);
18581 let mut buffers = HashSet::default();
18582 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18583 BTreeMap::new();
18584 for selection_range in selection_ranges {
18585 for (buffer, buffer_range, _) in
18586 snapshot.range_to_buffer_ranges(selection_range)
18587 {
18588 let buffer_id = buffer.remote_id();
18589 let start = buffer.anchor_before(buffer_range.start);
18590 let end = buffer.anchor_after(buffer_range.end);
18591 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18592 buffer_id_to_ranges
18593 .entry(buffer_id)
18594 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18595 .or_insert_with(|| vec![start..end]);
18596 }
18597 }
18598 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18599 }
18600 };
18601
18602 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18603 let selections_prev = transaction_id_prev
18604 .and_then(|transaction_id_prev| {
18605 // default to selections as they were after the last edit, if we have them,
18606 // instead of how they are now.
18607 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18608 // will take you back to where you made the last edit, instead of staying where you scrolled
18609 self.selection_history
18610 .transaction(transaction_id_prev)
18611 .map(|t| t.0.clone())
18612 })
18613 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18614
18615 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18616 let format = project.update(cx, |project, cx| {
18617 project.format(buffers, target, true, trigger, cx)
18618 });
18619
18620 cx.spawn_in(window, async move |editor, cx| {
18621 let transaction = futures::select_biased! {
18622 transaction = format.log_err().fuse() => transaction,
18623 () = timeout => {
18624 log::warn!("timed out waiting for formatting");
18625 None
18626 }
18627 };
18628
18629 buffer.update(cx, |buffer, cx| {
18630 if let Some(transaction) = transaction
18631 && !buffer.is_singleton()
18632 {
18633 buffer.push_transaction(&transaction.0, cx);
18634 }
18635 cx.notify();
18636 });
18637
18638 if let Some(transaction_id_now) =
18639 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18640 {
18641 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18642 if has_new_transaction {
18643 editor
18644 .update(cx, |editor, _| {
18645 editor
18646 .selection_history
18647 .insert_transaction(transaction_id_now, selections_prev);
18648 })
18649 .ok();
18650 }
18651 }
18652
18653 Ok(())
18654 })
18655 }
18656
18657 fn organize_imports(
18658 &mut self,
18659 _: &OrganizeImports,
18660 window: &mut Window,
18661 cx: &mut Context<Self>,
18662 ) -> Option<Task<Result<()>>> {
18663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18664 let project = match &self.project {
18665 Some(project) => project.clone(),
18666 None => return None,
18667 };
18668 Some(self.perform_code_action_kind(
18669 project,
18670 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18671 window,
18672 cx,
18673 ))
18674 }
18675
18676 fn perform_code_action_kind(
18677 &mut self,
18678 project: Entity<Project>,
18679 kind: CodeActionKind,
18680 window: &mut Window,
18681 cx: &mut Context<Self>,
18682 ) -> Task<Result<()>> {
18683 let buffer = self.buffer.clone();
18684 let buffers = buffer.read(cx).all_buffers();
18685 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18686 let apply_action = project.update(cx, |project, cx| {
18687 project.apply_code_action_kind(buffers, kind, true, cx)
18688 });
18689 cx.spawn_in(window, async move |_, cx| {
18690 let transaction = futures::select_biased! {
18691 () = timeout => {
18692 log::warn!("timed out waiting for executing code action");
18693 None
18694 }
18695 transaction = apply_action.log_err().fuse() => transaction,
18696 };
18697 buffer.update(cx, |buffer, cx| {
18698 // check if we need this
18699 if let Some(transaction) = transaction
18700 && !buffer.is_singleton()
18701 {
18702 buffer.push_transaction(&transaction.0, cx);
18703 }
18704 cx.notify();
18705 });
18706 Ok(())
18707 })
18708 }
18709
18710 pub fn restart_language_server(
18711 &mut self,
18712 _: &RestartLanguageServer,
18713 _: &mut Window,
18714 cx: &mut Context<Self>,
18715 ) {
18716 if let Some(project) = self.project.clone() {
18717 self.buffer.update(cx, |multi_buffer, cx| {
18718 project.update(cx, |project, cx| {
18719 project.restart_language_servers_for_buffers(
18720 multi_buffer.all_buffers().into_iter().collect(),
18721 HashSet::default(),
18722 cx,
18723 );
18724 });
18725 })
18726 }
18727 }
18728
18729 pub fn stop_language_server(
18730 &mut self,
18731 _: &StopLanguageServer,
18732 _: &mut Window,
18733 cx: &mut Context<Self>,
18734 ) {
18735 if let Some(project) = self.project.clone() {
18736 self.buffer.update(cx, |multi_buffer, cx| {
18737 project.update(cx, |project, cx| {
18738 project.stop_language_servers_for_buffers(
18739 multi_buffer.all_buffers().into_iter().collect(),
18740 HashSet::default(),
18741 cx,
18742 );
18743 });
18744 });
18745 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18746 }
18747 }
18748
18749 fn cancel_language_server_work(
18750 workspace: &mut Workspace,
18751 _: &actions::CancelLanguageServerWork,
18752 _: &mut Window,
18753 cx: &mut Context<Workspace>,
18754 ) {
18755 let project = workspace.project();
18756 let buffers = workspace
18757 .active_item(cx)
18758 .and_then(|item| item.act_as::<Editor>(cx))
18759 .map_or(HashSet::default(), |editor| {
18760 editor.read(cx).buffer.read(cx).all_buffers()
18761 });
18762 project.update(cx, |project, cx| {
18763 project.cancel_language_server_work_for_buffers(buffers, cx);
18764 });
18765 }
18766
18767 fn show_character_palette(
18768 &mut self,
18769 _: &ShowCharacterPalette,
18770 window: &mut Window,
18771 _: &mut Context<Self>,
18772 ) {
18773 window.show_character_palette();
18774 }
18775
18776 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18777 if !self.diagnostics_enabled() {
18778 return;
18779 }
18780
18781 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18782 let buffer = self.buffer.read(cx).snapshot(cx);
18783 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18784 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18785 let is_valid = buffer
18786 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18787 .any(|entry| {
18788 entry.diagnostic.is_primary
18789 && !entry.range.is_empty()
18790 && entry.range.start == primary_range_start
18791 && entry.diagnostic.message == active_diagnostics.active_message
18792 });
18793
18794 if !is_valid {
18795 self.dismiss_diagnostics(cx);
18796 }
18797 }
18798 }
18799
18800 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18801 match &self.active_diagnostics {
18802 ActiveDiagnostic::Group(group) => Some(group),
18803 _ => None,
18804 }
18805 }
18806
18807 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18808 if !self.diagnostics_enabled() {
18809 return;
18810 }
18811 self.dismiss_diagnostics(cx);
18812 self.active_diagnostics = ActiveDiagnostic::All;
18813 }
18814
18815 fn activate_diagnostics(
18816 &mut self,
18817 buffer_id: BufferId,
18818 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18819 window: &mut Window,
18820 cx: &mut Context<Self>,
18821 ) {
18822 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18823 return;
18824 }
18825 self.dismiss_diagnostics(cx);
18826 let snapshot = self.snapshot(window, cx);
18827 let buffer = self.buffer.read(cx).snapshot(cx);
18828 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18829 return;
18830 };
18831
18832 let diagnostic_group = buffer
18833 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18834 .collect::<Vec<_>>();
18835
18836 let language_registry = self
18837 .project()
18838 .map(|project| project.read(cx).languages().clone());
18839
18840 let blocks = renderer.render_group(
18841 diagnostic_group,
18842 buffer_id,
18843 snapshot,
18844 cx.weak_entity(),
18845 language_registry,
18846 cx,
18847 );
18848
18849 let blocks = self.display_map.update(cx, |display_map, cx| {
18850 display_map.insert_blocks(blocks, cx).into_iter().collect()
18851 });
18852 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18853 active_range: buffer.anchor_before(diagnostic.range.start)
18854 ..buffer.anchor_after(diagnostic.range.end),
18855 active_message: diagnostic.diagnostic.message.clone(),
18856 group_id: diagnostic.diagnostic.group_id,
18857 blocks,
18858 });
18859 cx.notify();
18860 }
18861
18862 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18863 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18864 return;
18865 };
18866
18867 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18868 if let ActiveDiagnostic::Group(group) = prev {
18869 self.display_map.update(cx, |display_map, cx| {
18870 display_map.remove_blocks(group.blocks, cx);
18871 });
18872 cx.notify();
18873 }
18874 }
18875
18876 /// Disable inline diagnostics rendering for this editor.
18877 pub fn disable_inline_diagnostics(&mut self) {
18878 self.inline_diagnostics_enabled = false;
18879 self.inline_diagnostics_update = Task::ready(());
18880 self.inline_diagnostics.clear();
18881 }
18882
18883 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18884 self.diagnostics_enabled = false;
18885 self.dismiss_diagnostics(cx);
18886 self.inline_diagnostics_update = Task::ready(());
18887 self.inline_diagnostics.clear();
18888 }
18889
18890 pub fn disable_word_completions(&mut self) {
18891 self.word_completions_enabled = false;
18892 }
18893
18894 pub fn diagnostics_enabled(&self) -> bool {
18895 self.diagnostics_enabled && self.mode.is_full()
18896 }
18897
18898 pub fn inline_diagnostics_enabled(&self) -> bool {
18899 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18900 }
18901
18902 pub fn show_inline_diagnostics(&self) -> bool {
18903 self.show_inline_diagnostics
18904 }
18905
18906 pub fn toggle_inline_diagnostics(
18907 &mut self,
18908 _: &ToggleInlineDiagnostics,
18909 window: &mut Window,
18910 cx: &mut Context<Editor>,
18911 ) {
18912 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18913 self.refresh_inline_diagnostics(false, window, cx);
18914 }
18915
18916 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18917 self.diagnostics_max_severity = severity;
18918 self.display_map.update(cx, |display_map, _| {
18919 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18920 });
18921 }
18922
18923 pub fn toggle_diagnostics(
18924 &mut self,
18925 _: &ToggleDiagnostics,
18926 window: &mut Window,
18927 cx: &mut Context<Editor>,
18928 ) {
18929 if !self.diagnostics_enabled() {
18930 return;
18931 }
18932
18933 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18934 EditorSettings::get_global(cx)
18935 .diagnostics_max_severity
18936 .filter(|severity| severity != &DiagnosticSeverity::Off)
18937 .unwrap_or(DiagnosticSeverity::Hint)
18938 } else {
18939 DiagnosticSeverity::Off
18940 };
18941 self.set_max_diagnostics_severity(new_severity, cx);
18942 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18943 self.active_diagnostics = ActiveDiagnostic::None;
18944 self.inline_diagnostics_update = Task::ready(());
18945 self.inline_diagnostics.clear();
18946 } else {
18947 self.refresh_inline_diagnostics(false, window, cx);
18948 }
18949
18950 cx.notify();
18951 }
18952
18953 pub fn toggle_minimap(
18954 &mut self,
18955 _: &ToggleMinimap,
18956 window: &mut Window,
18957 cx: &mut Context<Editor>,
18958 ) {
18959 if self.supports_minimap(cx) {
18960 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18961 }
18962 }
18963
18964 fn refresh_inline_diagnostics(
18965 &mut self,
18966 debounce: bool,
18967 window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 let max_severity = ProjectSettings::get_global(cx)
18971 .diagnostics
18972 .inline
18973 .max_severity
18974 .unwrap_or(self.diagnostics_max_severity);
18975
18976 if !self.inline_diagnostics_enabled()
18977 || !self.diagnostics_enabled()
18978 || !self.show_inline_diagnostics
18979 || max_severity == DiagnosticSeverity::Off
18980 {
18981 self.inline_diagnostics_update = Task::ready(());
18982 self.inline_diagnostics.clear();
18983 return;
18984 }
18985
18986 let debounce_ms = ProjectSettings::get_global(cx)
18987 .diagnostics
18988 .inline
18989 .update_debounce_ms;
18990 let debounce = if debounce && debounce_ms > 0 {
18991 Some(Duration::from_millis(debounce_ms))
18992 } else {
18993 None
18994 };
18995 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18996 if let Some(debounce) = debounce {
18997 cx.background_executor().timer(debounce).await;
18998 }
18999 let Some(snapshot) = editor.upgrade().map(|editor| {
19000 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19001 }) else {
19002 return;
19003 };
19004
19005 let new_inline_diagnostics = cx
19006 .background_spawn(async move {
19007 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19008 for diagnostic_entry in
19009 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19010 {
19011 let message = diagnostic_entry
19012 .diagnostic
19013 .message
19014 .split_once('\n')
19015 .map(|(line, _)| line)
19016 .map(SharedString::new)
19017 .unwrap_or_else(|| {
19018 SharedString::new(&*diagnostic_entry.diagnostic.message)
19019 });
19020 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19021 let (Ok(i) | Err(i)) = inline_diagnostics
19022 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19023 inline_diagnostics.insert(
19024 i,
19025 (
19026 start_anchor,
19027 InlineDiagnostic {
19028 message,
19029 group_id: diagnostic_entry.diagnostic.group_id,
19030 start: diagnostic_entry.range.start.to_point(&snapshot),
19031 is_primary: diagnostic_entry.diagnostic.is_primary,
19032 severity: diagnostic_entry.diagnostic.severity,
19033 },
19034 ),
19035 );
19036 }
19037 inline_diagnostics
19038 })
19039 .await;
19040
19041 editor
19042 .update(cx, |editor, cx| {
19043 editor.inline_diagnostics = new_inline_diagnostics;
19044 cx.notify();
19045 })
19046 .ok();
19047 });
19048 }
19049
19050 fn pull_diagnostics(
19051 &mut self,
19052 buffer_id: Option<BufferId>,
19053 window: &Window,
19054 cx: &mut Context<Self>,
19055 ) -> Option<()> {
19056 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
19057 return None;
19058 }
19059 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19060 .diagnostics
19061 .lsp_pull_diagnostics;
19062 if !pull_diagnostics_settings.enabled {
19063 return None;
19064 }
19065 let project = self.project()?.downgrade();
19066
19067 let mut edited_buffer_ids = HashSet::default();
19068 let mut edited_worktree_ids = HashSet::default();
19069 let edited_buffers = match buffer_id {
19070 Some(buffer_id) => {
19071 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19072 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
19073 edited_buffer_ids.insert(buffer.read(cx).remote_id());
19074 edited_worktree_ids.insert(worktree_id);
19075 vec![buffer]
19076 }
19077 None => self
19078 .buffer()
19079 .read(cx)
19080 .all_buffers()
19081 .into_iter()
19082 .filter(|buffer| {
19083 let buffer = buffer.read(cx);
19084 match buffer.file().map(|f| f.worktree_id(cx)) {
19085 Some(worktree_id) => {
19086 edited_buffer_ids.insert(buffer.remote_id());
19087 edited_worktree_ids.insert(worktree_id);
19088 true
19089 }
19090 None => false,
19091 }
19092 })
19093 .collect::<Vec<_>>(),
19094 };
19095
19096 if edited_buffers.is_empty() {
19097 self.pull_diagnostics_task = Task::ready(());
19098 self.pull_diagnostics_background_task = Task::ready(());
19099 return None;
19100 }
19101
19102 let mut already_used_buffers = HashSet::default();
19103 let related_open_buffers = self
19104 .workspace
19105 .as_ref()
19106 .and_then(|(workspace, _)| workspace.upgrade())
19107 .into_iter()
19108 .flat_map(|workspace| workspace.read(cx).panes())
19109 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
19110 .filter(|editor| editor != &cx.entity())
19111 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
19112 .filter(|buffer| {
19113 let buffer = buffer.read(cx);
19114 let buffer_id = buffer.remote_id();
19115 if already_used_buffers.insert(buffer_id) {
19116 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
19117 return !edited_buffer_ids.contains(&buffer_id)
19118 && edited_worktree_ids.contains(&worktree_id);
19119 }
19120 }
19121 false
19122 })
19123 .collect::<Vec<_>>();
19124
19125 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19126 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
19127 if buffers.is_empty() {
19128 return Task::ready(());
19129 }
19130 let project_weak = project.clone();
19131 cx.spawn_in(window, async move |_, cx| {
19132 cx.background_executor().timer(delay).await;
19133
19134 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
19135 buffers
19136 .into_iter()
19137 .filter_map(|buffer| {
19138 project_weak
19139 .update(cx, |project, cx| {
19140 project.lsp_store().update(cx, |lsp_store, cx| {
19141 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19142 })
19143 })
19144 .ok()
19145 })
19146 .collect::<FuturesUnordered<_>>()
19147 }) else {
19148 return;
19149 };
19150
19151 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
19152 if let Err(e) = pull_task {
19153 log::error!("Failed to update project diagnostics: {e:#}");
19154 }
19155 }
19156 })
19157 };
19158
19159 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
19160 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
19161
19162 Some(())
19163 }
19164
19165 pub fn set_selections_from_remote(
19166 &mut self,
19167 selections: Vec<Selection<Anchor>>,
19168 pending_selection: Option<Selection<Anchor>>,
19169 window: &mut Window,
19170 cx: &mut Context<Self>,
19171 ) {
19172 let old_cursor_position = self.selections.newest_anchor().head();
19173 self.selections
19174 .change_with(&self.display_snapshot(cx), |s| {
19175 s.select_anchors(selections);
19176 if let Some(pending_selection) = pending_selection {
19177 s.set_pending(pending_selection, SelectMode::Character);
19178 } else {
19179 s.clear_pending();
19180 }
19181 });
19182 self.selections_did_change(
19183 false,
19184 &old_cursor_position,
19185 SelectionEffects::default(),
19186 window,
19187 cx,
19188 );
19189 }
19190
19191 pub fn transact(
19192 &mut self,
19193 window: &mut Window,
19194 cx: &mut Context<Self>,
19195 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19196 ) -> Option<TransactionId> {
19197 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19198 this.start_transaction_at(Instant::now(), window, cx);
19199 update(this, window, cx);
19200 this.end_transaction_at(Instant::now(), cx)
19201 })
19202 }
19203
19204 pub fn start_transaction_at(
19205 &mut self,
19206 now: Instant,
19207 window: &mut Window,
19208 cx: &mut Context<Self>,
19209 ) -> Option<TransactionId> {
19210 self.end_selection(window, cx);
19211 if let Some(tx_id) = self
19212 .buffer
19213 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19214 {
19215 self.selection_history
19216 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19217 cx.emit(EditorEvent::TransactionBegun {
19218 transaction_id: tx_id,
19219 });
19220 Some(tx_id)
19221 } else {
19222 None
19223 }
19224 }
19225
19226 pub fn end_transaction_at(
19227 &mut self,
19228 now: Instant,
19229 cx: &mut Context<Self>,
19230 ) -> Option<TransactionId> {
19231 if let Some(transaction_id) = self
19232 .buffer
19233 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19234 {
19235 if let Some((_, end_selections)) =
19236 self.selection_history.transaction_mut(transaction_id)
19237 {
19238 *end_selections = Some(self.selections.disjoint_anchors_arc());
19239 } else {
19240 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19241 }
19242
19243 cx.emit(EditorEvent::Edited { transaction_id });
19244 Some(transaction_id)
19245 } else {
19246 None
19247 }
19248 }
19249
19250 pub fn modify_transaction_selection_history(
19251 &mut self,
19252 transaction_id: TransactionId,
19253 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19254 ) -> bool {
19255 self.selection_history
19256 .transaction_mut(transaction_id)
19257 .map(modify)
19258 .is_some()
19259 }
19260
19261 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19262 if self.selection_mark_mode {
19263 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19264 s.move_with(|_, sel| {
19265 sel.collapse_to(sel.head(), SelectionGoal::None);
19266 });
19267 })
19268 }
19269 self.selection_mark_mode = true;
19270 cx.notify();
19271 }
19272
19273 pub fn swap_selection_ends(
19274 &mut self,
19275 _: &actions::SwapSelectionEnds,
19276 window: &mut Window,
19277 cx: &mut Context<Self>,
19278 ) {
19279 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19280 s.move_with(|_, sel| {
19281 if sel.start != sel.end {
19282 sel.reversed = !sel.reversed
19283 }
19284 });
19285 });
19286 self.request_autoscroll(Autoscroll::newest(), cx);
19287 cx.notify();
19288 }
19289
19290 pub fn toggle_focus(
19291 workspace: &mut Workspace,
19292 _: &actions::ToggleFocus,
19293 window: &mut Window,
19294 cx: &mut Context<Workspace>,
19295 ) {
19296 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19297 return;
19298 };
19299 workspace.activate_item(&item, true, true, window, cx);
19300 }
19301
19302 pub fn toggle_fold(
19303 &mut self,
19304 _: &actions::ToggleFold,
19305 window: &mut Window,
19306 cx: &mut Context<Self>,
19307 ) {
19308 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19310 let selection = self.selections.newest::<Point>(&display_map);
19311
19312 let range = if selection.is_empty() {
19313 let point = selection.head().to_display_point(&display_map);
19314 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19315 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19316 .to_point(&display_map);
19317 start..end
19318 } else {
19319 selection.range()
19320 };
19321 if display_map.folds_in_range(range).next().is_some() {
19322 self.unfold_lines(&Default::default(), window, cx)
19323 } else {
19324 self.fold(&Default::default(), window, cx)
19325 }
19326 } else {
19327 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19328 let buffer_ids: HashSet<_> = self
19329 .selections
19330 .disjoint_anchor_ranges()
19331 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19332 .collect();
19333
19334 let should_unfold = buffer_ids
19335 .iter()
19336 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19337
19338 for buffer_id in buffer_ids {
19339 if should_unfold {
19340 self.unfold_buffer(buffer_id, cx);
19341 } else {
19342 self.fold_buffer(buffer_id, cx);
19343 }
19344 }
19345 }
19346 }
19347
19348 pub fn toggle_fold_recursive(
19349 &mut self,
19350 _: &actions::ToggleFoldRecursive,
19351 window: &mut Window,
19352 cx: &mut Context<Self>,
19353 ) {
19354 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19355
19356 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19357 let range = if selection.is_empty() {
19358 let point = selection.head().to_display_point(&display_map);
19359 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19360 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19361 .to_point(&display_map);
19362 start..end
19363 } else {
19364 selection.range()
19365 };
19366 if display_map.folds_in_range(range).next().is_some() {
19367 self.unfold_recursive(&Default::default(), window, cx)
19368 } else {
19369 self.fold_recursive(&Default::default(), window, cx)
19370 }
19371 }
19372
19373 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19374 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19375 let mut to_fold = Vec::new();
19376 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19377 let selections = self.selections.all_adjusted(&display_map);
19378
19379 for selection in selections {
19380 let range = selection.range().sorted();
19381 let buffer_start_row = range.start.row;
19382
19383 if range.start.row != range.end.row {
19384 let mut found = false;
19385 let mut row = range.start.row;
19386 while row <= range.end.row {
19387 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19388 {
19389 found = true;
19390 row = crease.range().end.row + 1;
19391 to_fold.push(crease);
19392 } else {
19393 row += 1
19394 }
19395 }
19396 if found {
19397 continue;
19398 }
19399 }
19400
19401 for row in (0..=range.start.row).rev() {
19402 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19403 && crease.range().end.row >= buffer_start_row
19404 {
19405 to_fold.push(crease);
19406 if row <= range.start.row {
19407 break;
19408 }
19409 }
19410 }
19411 }
19412
19413 self.fold_creases(to_fold, true, window, cx);
19414 } else {
19415 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19416 let buffer_ids = self
19417 .selections
19418 .disjoint_anchor_ranges()
19419 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19420 .collect::<HashSet<_>>();
19421 for buffer_id in buffer_ids {
19422 self.fold_buffer(buffer_id, cx);
19423 }
19424 }
19425 }
19426
19427 pub fn toggle_fold_all(
19428 &mut self,
19429 _: &actions::ToggleFoldAll,
19430 window: &mut Window,
19431 cx: &mut Context<Self>,
19432 ) {
19433 let has_folds = if self.buffer.read(cx).is_singleton() {
19434 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19435 let has_folds = display_map
19436 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19437 .next()
19438 .is_some();
19439 has_folds
19440 } else {
19441 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19442 let has_folds = buffer_ids
19443 .iter()
19444 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19445 has_folds
19446 };
19447
19448 if has_folds {
19449 self.unfold_all(&actions::UnfoldAll, window, cx);
19450 } else {
19451 self.fold_all(&actions::FoldAll, window, cx);
19452 }
19453 }
19454
19455 fn fold_at_level(
19456 &mut self,
19457 fold_at: &FoldAtLevel,
19458 window: &mut Window,
19459 cx: &mut Context<Self>,
19460 ) {
19461 if !self.buffer.read(cx).is_singleton() {
19462 return;
19463 }
19464
19465 let fold_at_level = fold_at.0;
19466 let snapshot = self.buffer.read(cx).snapshot(cx);
19467 let mut to_fold = Vec::new();
19468 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19469
19470 let row_ranges_to_keep: Vec<Range<u32>> = self
19471 .selections
19472 .all::<Point>(&self.display_snapshot(cx))
19473 .into_iter()
19474 .map(|sel| sel.start.row..sel.end.row)
19475 .collect();
19476
19477 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19478 while start_row < end_row {
19479 match self
19480 .snapshot(window, cx)
19481 .crease_for_buffer_row(MultiBufferRow(start_row))
19482 {
19483 Some(crease) => {
19484 let nested_start_row = crease.range().start.row + 1;
19485 let nested_end_row = crease.range().end.row;
19486
19487 if current_level < fold_at_level {
19488 stack.push((nested_start_row, nested_end_row, current_level + 1));
19489 } else if current_level == fold_at_level {
19490 // Fold iff there is no selection completely contained within the fold region
19491 if !row_ranges_to_keep.iter().any(|selection| {
19492 selection.end >= nested_start_row
19493 && selection.start <= nested_end_row
19494 }) {
19495 to_fold.push(crease);
19496 }
19497 }
19498
19499 start_row = nested_end_row + 1;
19500 }
19501 None => start_row += 1,
19502 }
19503 }
19504 }
19505
19506 self.fold_creases(to_fold, true, window, cx);
19507 }
19508
19509 pub fn fold_at_level_1(
19510 &mut self,
19511 _: &actions::FoldAtLevel1,
19512 window: &mut Window,
19513 cx: &mut Context<Self>,
19514 ) {
19515 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19516 }
19517
19518 pub fn fold_at_level_2(
19519 &mut self,
19520 _: &actions::FoldAtLevel2,
19521 window: &mut Window,
19522 cx: &mut Context<Self>,
19523 ) {
19524 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19525 }
19526
19527 pub fn fold_at_level_3(
19528 &mut self,
19529 _: &actions::FoldAtLevel3,
19530 window: &mut Window,
19531 cx: &mut Context<Self>,
19532 ) {
19533 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19534 }
19535
19536 pub fn fold_at_level_4(
19537 &mut self,
19538 _: &actions::FoldAtLevel4,
19539 window: &mut Window,
19540 cx: &mut Context<Self>,
19541 ) {
19542 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19543 }
19544
19545 pub fn fold_at_level_5(
19546 &mut self,
19547 _: &actions::FoldAtLevel5,
19548 window: &mut Window,
19549 cx: &mut Context<Self>,
19550 ) {
19551 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19552 }
19553
19554 pub fn fold_at_level_6(
19555 &mut self,
19556 _: &actions::FoldAtLevel6,
19557 window: &mut Window,
19558 cx: &mut Context<Self>,
19559 ) {
19560 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19561 }
19562
19563 pub fn fold_at_level_7(
19564 &mut self,
19565 _: &actions::FoldAtLevel7,
19566 window: &mut Window,
19567 cx: &mut Context<Self>,
19568 ) {
19569 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19570 }
19571
19572 pub fn fold_at_level_8(
19573 &mut self,
19574 _: &actions::FoldAtLevel8,
19575 window: &mut Window,
19576 cx: &mut Context<Self>,
19577 ) {
19578 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19579 }
19580
19581 pub fn fold_at_level_9(
19582 &mut self,
19583 _: &actions::FoldAtLevel9,
19584 window: &mut Window,
19585 cx: &mut Context<Self>,
19586 ) {
19587 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19588 }
19589
19590 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19591 if self.buffer.read(cx).is_singleton() {
19592 let mut fold_ranges = Vec::new();
19593 let snapshot = self.buffer.read(cx).snapshot(cx);
19594
19595 for row in 0..snapshot.max_row().0 {
19596 if let Some(foldable_range) = self
19597 .snapshot(window, cx)
19598 .crease_for_buffer_row(MultiBufferRow(row))
19599 {
19600 fold_ranges.push(foldable_range);
19601 }
19602 }
19603
19604 self.fold_creases(fold_ranges, true, window, cx);
19605 } else {
19606 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19607 editor
19608 .update_in(cx, |editor, _, cx| {
19609 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19610 editor.fold_buffer(buffer_id, cx);
19611 }
19612 })
19613 .ok();
19614 });
19615 }
19616 cx.emit(SearchEvent::ResultsCollapsedChanged(
19617 CollapseDirection::Collapsed,
19618 ));
19619 }
19620
19621 pub fn fold_function_bodies(
19622 &mut self,
19623 _: &actions::FoldFunctionBodies,
19624 window: &mut Window,
19625 cx: &mut Context<Self>,
19626 ) {
19627 let snapshot = self.buffer.read(cx).snapshot(cx);
19628
19629 let ranges = snapshot
19630 .text_object_ranges(
19631 MultiBufferOffset(0)..snapshot.len(),
19632 TreeSitterOptions::default(),
19633 )
19634 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19635 .collect::<Vec<_>>();
19636
19637 let creases = ranges
19638 .into_iter()
19639 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19640 .collect();
19641
19642 self.fold_creases(creases, true, window, cx);
19643 }
19644
19645 pub fn fold_recursive(
19646 &mut self,
19647 _: &actions::FoldRecursive,
19648 window: &mut Window,
19649 cx: &mut Context<Self>,
19650 ) {
19651 let mut to_fold = Vec::new();
19652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19653 let selections = self.selections.all_adjusted(&display_map);
19654
19655 for selection in selections {
19656 let range = selection.range().sorted();
19657 let buffer_start_row = range.start.row;
19658
19659 if range.start.row != range.end.row {
19660 let mut found = false;
19661 for row in range.start.row..=range.end.row {
19662 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19663 found = true;
19664 to_fold.push(crease);
19665 }
19666 }
19667 if found {
19668 continue;
19669 }
19670 }
19671
19672 for row in (0..=range.start.row).rev() {
19673 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19674 if crease.range().end.row >= buffer_start_row {
19675 to_fold.push(crease);
19676 } else {
19677 break;
19678 }
19679 }
19680 }
19681 }
19682
19683 self.fold_creases(to_fold, true, window, cx);
19684 }
19685
19686 pub fn fold_at(
19687 &mut self,
19688 buffer_row: MultiBufferRow,
19689 window: &mut Window,
19690 cx: &mut Context<Self>,
19691 ) {
19692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19693
19694 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19695 let autoscroll = self
19696 .selections
19697 .all::<Point>(&display_map)
19698 .iter()
19699 .any(|selection| crease.range().overlaps(&selection.range()));
19700
19701 self.fold_creases(vec![crease], autoscroll, window, cx);
19702 }
19703 }
19704
19705 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19706 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19707 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19708 let buffer = display_map.buffer_snapshot();
19709 let selections = self.selections.all::<Point>(&display_map);
19710 let ranges = selections
19711 .iter()
19712 .map(|s| {
19713 let range = s.display_range(&display_map).sorted();
19714 let mut start = range.start.to_point(&display_map);
19715 let mut end = range.end.to_point(&display_map);
19716 start.column = 0;
19717 end.column = buffer.line_len(MultiBufferRow(end.row));
19718 start..end
19719 })
19720 .collect::<Vec<_>>();
19721
19722 self.unfold_ranges(&ranges, true, true, cx);
19723 } else {
19724 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19725 let buffer_ids = self
19726 .selections
19727 .disjoint_anchor_ranges()
19728 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19729 .collect::<HashSet<_>>();
19730 for buffer_id in buffer_ids {
19731 self.unfold_buffer(buffer_id, cx);
19732 }
19733 }
19734 }
19735
19736 pub fn unfold_recursive(
19737 &mut self,
19738 _: &UnfoldRecursive,
19739 _window: &mut Window,
19740 cx: &mut Context<Self>,
19741 ) {
19742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19743 let selections = self.selections.all::<Point>(&display_map);
19744 let ranges = selections
19745 .iter()
19746 .map(|s| {
19747 let mut range = s.display_range(&display_map).sorted();
19748 *range.start.column_mut() = 0;
19749 *range.end.column_mut() = display_map.line_len(range.end.row());
19750 let start = range.start.to_point(&display_map);
19751 let end = range.end.to_point(&display_map);
19752 start..end
19753 })
19754 .collect::<Vec<_>>();
19755
19756 self.unfold_ranges(&ranges, true, true, cx);
19757 }
19758
19759 pub fn unfold_at(
19760 &mut self,
19761 buffer_row: MultiBufferRow,
19762 _window: &mut Window,
19763 cx: &mut Context<Self>,
19764 ) {
19765 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19766
19767 let intersection_range = Point::new(buffer_row.0, 0)
19768 ..Point::new(
19769 buffer_row.0,
19770 display_map.buffer_snapshot().line_len(buffer_row),
19771 );
19772
19773 let autoscroll = self
19774 .selections
19775 .all::<Point>(&display_map)
19776 .iter()
19777 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19778
19779 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19780 }
19781
19782 pub fn unfold_all(
19783 &mut self,
19784 _: &actions::UnfoldAll,
19785 _window: &mut Window,
19786 cx: &mut Context<Self>,
19787 ) {
19788 if self.buffer.read(cx).is_singleton() {
19789 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19790 self.unfold_ranges(
19791 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19792 true,
19793 true,
19794 cx,
19795 );
19796 } else {
19797 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19798 editor
19799 .update(cx, |editor, cx| {
19800 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19801 editor.unfold_buffer(buffer_id, cx);
19802 }
19803 })
19804 .ok();
19805 });
19806 }
19807 cx.emit(SearchEvent::ResultsCollapsedChanged(
19808 CollapseDirection::Expanded,
19809 ));
19810 }
19811
19812 pub fn fold_selected_ranges(
19813 &mut self,
19814 _: &FoldSelectedRanges,
19815 window: &mut Window,
19816 cx: &mut Context<Self>,
19817 ) {
19818 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19819 let selections = self.selections.all_adjusted(&display_map);
19820 let ranges = selections
19821 .into_iter()
19822 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19823 .collect::<Vec<_>>();
19824 self.fold_creases(ranges, true, window, cx);
19825 }
19826
19827 pub fn fold_ranges<T: ToOffset + Clone>(
19828 &mut self,
19829 ranges: Vec<Range<T>>,
19830 auto_scroll: bool,
19831 window: &mut Window,
19832 cx: &mut Context<Self>,
19833 ) {
19834 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19835 let ranges = ranges
19836 .into_iter()
19837 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19838 .collect::<Vec<_>>();
19839 self.fold_creases(ranges, auto_scroll, window, cx);
19840 }
19841
19842 pub fn fold_creases<T: ToOffset + Clone>(
19843 &mut self,
19844 creases: Vec<Crease<T>>,
19845 auto_scroll: bool,
19846 _window: &mut Window,
19847 cx: &mut Context<Self>,
19848 ) {
19849 if creases.is_empty() {
19850 return;
19851 }
19852
19853 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19854
19855 if auto_scroll {
19856 self.request_autoscroll(Autoscroll::fit(), cx);
19857 }
19858
19859 cx.notify();
19860
19861 self.scrollbar_marker_state.dirty = true;
19862 self.folds_did_change(cx);
19863 }
19864
19865 /// Removes any folds whose ranges intersect any of the given ranges.
19866 pub fn unfold_ranges<T: ToOffset + Clone>(
19867 &mut self,
19868 ranges: &[Range<T>],
19869 inclusive: bool,
19870 auto_scroll: bool,
19871 cx: &mut Context<Self>,
19872 ) {
19873 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19874 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19875 });
19876 self.folds_did_change(cx);
19877 }
19878
19879 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19880 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19881 return;
19882 }
19883
19884 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19885 self.display_map.update(cx, |display_map, cx| {
19886 display_map.fold_buffers([buffer_id], cx)
19887 });
19888
19889 let snapshot = self.display_snapshot(cx);
19890 self.selections.change_with(&snapshot, |selections| {
19891 selections.remove_selections_from_buffer(buffer_id);
19892 });
19893
19894 cx.emit(EditorEvent::BufferFoldToggled {
19895 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19896 folded: true,
19897 });
19898 cx.notify();
19899 }
19900
19901 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19902 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19903 return;
19904 }
19905 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19906 self.display_map.update(cx, |display_map, cx| {
19907 display_map.unfold_buffers([buffer_id], cx);
19908 });
19909 cx.emit(EditorEvent::BufferFoldToggled {
19910 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19911 folded: false,
19912 });
19913 cx.notify();
19914 }
19915
19916 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19917 self.display_map.read(cx).is_buffer_folded(buffer)
19918 }
19919
19920 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19921 self.display_map.read(cx).folded_buffers()
19922 }
19923
19924 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19925 self.display_map.update(cx, |display_map, cx| {
19926 display_map.disable_header_for_buffer(buffer_id, cx);
19927 });
19928 cx.notify();
19929 }
19930
19931 /// Removes any folds with the given ranges.
19932 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19933 &mut self,
19934 ranges: &[Range<T>],
19935 type_id: TypeId,
19936 auto_scroll: bool,
19937 cx: &mut Context<Self>,
19938 ) {
19939 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19940 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19941 });
19942 self.folds_did_change(cx);
19943 }
19944
19945 fn remove_folds_with<T: ToOffset + Clone>(
19946 &mut self,
19947 ranges: &[Range<T>],
19948 auto_scroll: bool,
19949 cx: &mut Context<Self>,
19950 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19951 ) {
19952 if ranges.is_empty() {
19953 return;
19954 }
19955
19956 let mut buffers_affected = HashSet::default();
19957 let multi_buffer = self.buffer().read(cx);
19958 for range in ranges {
19959 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19960 buffers_affected.insert(buffer.read(cx).remote_id());
19961 };
19962 }
19963
19964 self.display_map.update(cx, update);
19965
19966 if auto_scroll {
19967 self.request_autoscroll(Autoscroll::fit(), cx);
19968 }
19969
19970 cx.notify();
19971 self.scrollbar_marker_state.dirty = true;
19972 self.active_indent_guides_state.dirty = true;
19973 }
19974
19975 pub fn update_renderer_widths(
19976 &mut self,
19977 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19978 cx: &mut Context<Self>,
19979 ) -> bool {
19980 self.display_map
19981 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19982 }
19983
19984 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19985 self.display_map.read(cx).fold_placeholder.clone()
19986 }
19987
19988 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19989 self.buffer.update(cx, |buffer, cx| {
19990 buffer.set_all_diff_hunks_expanded(cx);
19991 });
19992 }
19993
19994 pub fn expand_all_diff_hunks(
19995 &mut self,
19996 _: &ExpandAllDiffHunks,
19997 _window: &mut Window,
19998 cx: &mut Context<Self>,
19999 ) {
20000 self.buffer.update(cx, |buffer, cx| {
20001 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20002 });
20003 }
20004
20005 pub fn collapse_all_diff_hunks(
20006 &mut self,
20007 _: &CollapseAllDiffHunks,
20008 _window: &mut Window,
20009 cx: &mut Context<Self>,
20010 ) {
20011 self.buffer.update(cx, |buffer, cx| {
20012 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20013 });
20014 }
20015
20016 pub fn toggle_selected_diff_hunks(
20017 &mut self,
20018 _: &ToggleSelectedDiffHunks,
20019 _window: &mut Window,
20020 cx: &mut Context<Self>,
20021 ) {
20022 let ranges: Vec<_> = self
20023 .selections
20024 .disjoint_anchors()
20025 .iter()
20026 .map(|s| s.range())
20027 .collect();
20028 self.toggle_diff_hunks_in_ranges(ranges, cx);
20029 }
20030
20031 pub fn diff_hunks_in_ranges<'a>(
20032 &'a self,
20033 ranges: &'a [Range<Anchor>],
20034 buffer: &'a MultiBufferSnapshot,
20035 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20036 ranges.iter().flat_map(move |range| {
20037 let end_excerpt_id = range.end.excerpt_id;
20038 let range = range.to_point(buffer);
20039 let mut peek_end = range.end;
20040 if range.end.row < buffer.max_row().0 {
20041 peek_end = Point::new(range.end.row + 1, 0);
20042 }
20043 buffer
20044 .diff_hunks_in_range(range.start..peek_end)
20045 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20046 })
20047 }
20048
20049 pub fn has_stageable_diff_hunks_in_ranges(
20050 &self,
20051 ranges: &[Range<Anchor>],
20052 snapshot: &MultiBufferSnapshot,
20053 ) -> bool {
20054 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20055 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20056 }
20057
20058 pub fn toggle_staged_selected_diff_hunks(
20059 &mut self,
20060 _: &::git::ToggleStaged,
20061 _: &mut Window,
20062 cx: &mut Context<Self>,
20063 ) {
20064 let snapshot = self.buffer.read(cx).snapshot(cx);
20065 let ranges: Vec<_> = self
20066 .selections
20067 .disjoint_anchors()
20068 .iter()
20069 .map(|s| s.range())
20070 .collect();
20071 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20072 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20073 }
20074
20075 pub fn set_render_diff_hunk_controls(
20076 &mut self,
20077 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20078 cx: &mut Context<Self>,
20079 ) {
20080 self.render_diff_hunk_controls = render_diff_hunk_controls;
20081 cx.notify();
20082 }
20083
20084 pub fn stage_and_next(
20085 &mut self,
20086 _: &::git::StageAndNext,
20087 window: &mut Window,
20088 cx: &mut Context<Self>,
20089 ) {
20090 self.do_stage_or_unstage_and_next(true, window, cx);
20091 }
20092
20093 pub fn unstage_and_next(
20094 &mut self,
20095 _: &::git::UnstageAndNext,
20096 window: &mut Window,
20097 cx: &mut Context<Self>,
20098 ) {
20099 self.do_stage_or_unstage_and_next(false, window, cx);
20100 }
20101
20102 pub fn stage_or_unstage_diff_hunks(
20103 &mut self,
20104 stage: bool,
20105 ranges: Vec<Range<Anchor>>,
20106 cx: &mut Context<Self>,
20107 ) {
20108 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20109 cx.spawn(async move |this, cx| {
20110 task.await?;
20111 this.update(cx, |this, cx| {
20112 let snapshot = this.buffer.read(cx).snapshot(cx);
20113 let chunk_by = this
20114 .diff_hunks_in_ranges(&ranges, &snapshot)
20115 .chunk_by(|hunk| hunk.buffer_id);
20116 for (buffer_id, hunks) in &chunk_by {
20117 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20118 }
20119 })
20120 })
20121 .detach_and_log_err(cx);
20122 }
20123
20124 fn save_buffers_for_ranges_if_needed(
20125 &mut self,
20126 ranges: &[Range<Anchor>],
20127 cx: &mut Context<Editor>,
20128 ) -> Task<Result<()>> {
20129 let multibuffer = self.buffer.read(cx);
20130 let snapshot = multibuffer.read(cx);
20131 let buffer_ids: HashSet<_> = ranges
20132 .iter()
20133 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20134 .collect();
20135 drop(snapshot);
20136
20137 let mut buffers = HashSet::default();
20138 for buffer_id in buffer_ids {
20139 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20140 let buffer = buffer_entity.read(cx);
20141 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20142 {
20143 buffers.insert(buffer_entity);
20144 }
20145 }
20146 }
20147
20148 if let Some(project) = &self.project {
20149 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20150 } else {
20151 Task::ready(Ok(()))
20152 }
20153 }
20154
20155 fn do_stage_or_unstage_and_next(
20156 &mut self,
20157 stage: bool,
20158 window: &mut Window,
20159 cx: &mut Context<Self>,
20160 ) {
20161 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20162
20163 if ranges.iter().any(|range| range.start != range.end) {
20164 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20165 return;
20166 }
20167
20168 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20169 let snapshot = self.snapshot(window, cx);
20170 let position = self
20171 .selections
20172 .newest::<Point>(&snapshot.display_snapshot)
20173 .head();
20174 let mut row = snapshot
20175 .buffer_snapshot()
20176 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20177 .find(|hunk| hunk.row_range.start.0 > position.row)
20178 .map(|hunk| hunk.row_range.start);
20179
20180 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20181 // Outside of the project diff editor, wrap around to the beginning.
20182 if !all_diff_hunks_expanded {
20183 row = row.or_else(|| {
20184 snapshot
20185 .buffer_snapshot()
20186 .diff_hunks_in_range(Point::zero()..position)
20187 .find(|hunk| hunk.row_range.end.0 < position.row)
20188 .map(|hunk| hunk.row_range.start)
20189 });
20190 }
20191
20192 if let Some(row) = row {
20193 let destination = Point::new(row.0, 0);
20194 let autoscroll = Autoscroll::center();
20195
20196 self.unfold_ranges(&[destination..destination], false, false, cx);
20197 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20198 s.select_ranges([destination..destination]);
20199 });
20200 }
20201 }
20202
20203 fn do_stage_or_unstage(
20204 &self,
20205 stage: bool,
20206 buffer_id: BufferId,
20207 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20208 cx: &mut App,
20209 ) -> Option<()> {
20210 let project = self.project()?;
20211 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20212 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20213 let buffer_snapshot = buffer.read(cx).snapshot();
20214 let file_exists = buffer_snapshot
20215 .file()
20216 .is_some_and(|file| file.disk_state().exists());
20217 diff.update(cx, |diff, cx| {
20218 diff.stage_or_unstage_hunks(
20219 stage,
20220 &hunks
20221 .map(|hunk| buffer_diff::DiffHunk {
20222 buffer_range: hunk.buffer_range,
20223 // We don't need to pass in word diffs here because they're only used for rendering and
20224 // this function changes internal state
20225 base_word_diffs: Vec::default(),
20226 buffer_word_diffs: Vec::default(),
20227 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20228 ..hunk.diff_base_byte_range.end.0,
20229 secondary_status: hunk.status.secondary,
20230 range: Point::zero()..Point::zero(), // unused
20231 })
20232 .collect::<Vec<_>>(),
20233 &buffer_snapshot,
20234 file_exists,
20235 cx,
20236 )
20237 });
20238 None
20239 }
20240
20241 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20242 let ranges: Vec<_> = self
20243 .selections
20244 .disjoint_anchors()
20245 .iter()
20246 .map(|s| s.range())
20247 .collect();
20248 self.buffer
20249 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20250 }
20251
20252 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20253 self.buffer.update(cx, |buffer, cx| {
20254 let ranges = vec![Anchor::min()..Anchor::max()];
20255 if !buffer.all_diff_hunks_expanded()
20256 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20257 {
20258 buffer.collapse_diff_hunks(ranges, cx);
20259 true
20260 } else {
20261 false
20262 }
20263 })
20264 }
20265
20266 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20267 if self.buffer.read(cx).all_diff_hunks_expanded() {
20268 return true;
20269 }
20270 let ranges = vec![Anchor::min()..Anchor::max()];
20271 self.buffer
20272 .read(cx)
20273 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20274 }
20275
20276 fn toggle_diff_hunks_in_ranges(
20277 &mut self,
20278 ranges: Vec<Range<Anchor>>,
20279 cx: &mut Context<Editor>,
20280 ) {
20281 self.buffer.update(cx, |buffer, cx| {
20282 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20283 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20284 })
20285 }
20286
20287 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20288 self.buffer.update(cx, |buffer, cx| {
20289 let snapshot = buffer.snapshot(cx);
20290 let excerpt_id = range.end.excerpt_id;
20291 let point_range = range.to_point(&snapshot);
20292 let expand = !buffer.single_hunk_is_expanded(range, cx);
20293 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20294 })
20295 }
20296
20297 pub(crate) fn apply_all_diff_hunks(
20298 &mut self,
20299 _: &ApplyAllDiffHunks,
20300 window: &mut Window,
20301 cx: &mut Context<Self>,
20302 ) {
20303 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20304
20305 let buffers = self.buffer.read(cx).all_buffers();
20306 for branch_buffer in buffers {
20307 branch_buffer.update(cx, |branch_buffer, cx| {
20308 branch_buffer.merge_into_base(Vec::new(), cx);
20309 });
20310 }
20311
20312 if let Some(project) = self.project.clone() {
20313 self.save(
20314 SaveOptions {
20315 format: true,
20316 autosave: false,
20317 },
20318 project,
20319 window,
20320 cx,
20321 )
20322 .detach_and_log_err(cx);
20323 }
20324 }
20325
20326 pub(crate) fn apply_selected_diff_hunks(
20327 &mut self,
20328 _: &ApplyDiffHunk,
20329 window: &mut Window,
20330 cx: &mut Context<Self>,
20331 ) {
20332 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20333 let snapshot = self.snapshot(window, cx);
20334 let hunks = snapshot.hunks_for_ranges(
20335 self.selections
20336 .all(&snapshot.display_snapshot)
20337 .into_iter()
20338 .map(|selection| selection.range()),
20339 );
20340 let mut ranges_by_buffer = HashMap::default();
20341 self.transact(window, cx, |editor, _window, cx| {
20342 for hunk in hunks {
20343 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20344 ranges_by_buffer
20345 .entry(buffer.clone())
20346 .or_insert_with(Vec::new)
20347 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20348 }
20349 }
20350
20351 for (buffer, ranges) in ranges_by_buffer {
20352 buffer.update(cx, |buffer, cx| {
20353 buffer.merge_into_base(ranges, cx);
20354 });
20355 }
20356 });
20357
20358 if let Some(project) = self.project.clone() {
20359 self.save(
20360 SaveOptions {
20361 format: true,
20362 autosave: false,
20363 },
20364 project,
20365 window,
20366 cx,
20367 )
20368 .detach_and_log_err(cx);
20369 }
20370 }
20371
20372 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20373 if hovered != self.gutter_hovered {
20374 self.gutter_hovered = hovered;
20375 cx.notify();
20376 }
20377 }
20378
20379 pub fn insert_blocks(
20380 &mut self,
20381 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20382 autoscroll: Option<Autoscroll>,
20383 cx: &mut Context<Self>,
20384 ) -> Vec<CustomBlockId> {
20385 let blocks = self
20386 .display_map
20387 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20388 if let Some(autoscroll) = autoscroll {
20389 self.request_autoscroll(autoscroll, cx);
20390 }
20391 cx.notify();
20392 blocks
20393 }
20394
20395 pub fn resize_blocks(
20396 &mut self,
20397 heights: HashMap<CustomBlockId, u32>,
20398 autoscroll: Option<Autoscroll>,
20399 cx: &mut Context<Self>,
20400 ) {
20401 self.display_map
20402 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20403 if let Some(autoscroll) = autoscroll {
20404 self.request_autoscroll(autoscroll, cx);
20405 }
20406 cx.notify();
20407 }
20408
20409 pub fn replace_blocks(
20410 &mut self,
20411 renderers: HashMap<CustomBlockId, RenderBlock>,
20412 autoscroll: Option<Autoscroll>,
20413 cx: &mut Context<Self>,
20414 ) {
20415 self.display_map
20416 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20417 if let Some(autoscroll) = autoscroll {
20418 self.request_autoscroll(autoscroll, cx);
20419 }
20420 cx.notify();
20421 }
20422
20423 pub fn remove_blocks(
20424 &mut self,
20425 block_ids: HashSet<CustomBlockId>,
20426 autoscroll: Option<Autoscroll>,
20427 cx: &mut Context<Self>,
20428 ) {
20429 self.display_map.update(cx, |display_map, cx| {
20430 display_map.remove_blocks(block_ids, cx)
20431 });
20432 if let Some(autoscroll) = autoscroll {
20433 self.request_autoscroll(autoscroll, cx);
20434 }
20435 cx.notify();
20436 }
20437
20438 pub fn row_for_block(
20439 &self,
20440 block_id: CustomBlockId,
20441 cx: &mut Context<Self>,
20442 ) -> Option<DisplayRow> {
20443 self.display_map
20444 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20445 }
20446
20447 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20448 self.focused_block = Some(focused_block);
20449 }
20450
20451 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20452 self.focused_block.take()
20453 }
20454
20455 pub fn insert_creases(
20456 &mut self,
20457 creases: impl IntoIterator<Item = Crease<Anchor>>,
20458 cx: &mut Context<Self>,
20459 ) -> Vec<CreaseId> {
20460 self.display_map
20461 .update(cx, |map, cx| map.insert_creases(creases, cx))
20462 }
20463
20464 pub fn remove_creases(
20465 &mut self,
20466 ids: impl IntoIterator<Item = CreaseId>,
20467 cx: &mut Context<Self>,
20468 ) -> Vec<(CreaseId, Range<Anchor>)> {
20469 self.display_map
20470 .update(cx, |map, cx| map.remove_creases(ids, cx))
20471 }
20472
20473 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20474 self.display_map
20475 .update(cx, |map, cx| map.snapshot(cx))
20476 .longest_row()
20477 }
20478
20479 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20480 self.display_map
20481 .update(cx, |map, cx| map.snapshot(cx))
20482 .max_point()
20483 }
20484
20485 pub fn text(&self, cx: &App) -> String {
20486 self.buffer.read(cx).read(cx).text()
20487 }
20488
20489 pub fn is_empty(&self, cx: &App) -> bool {
20490 self.buffer.read(cx).read(cx).is_empty()
20491 }
20492
20493 pub fn text_option(&self, cx: &App) -> Option<String> {
20494 let text = self.text(cx);
20495 let text = text.trim();
20496
20497 if text.is_empty() {
20498 return None;
20499 }
20500
20501 Some(text.to_string())
20502 }
20503
20504 pub fn set_text(
20505 &mut self,
20506 text: impl Into<Arc<str>>,
20507 window: &mut Window,
20508 cx: &mut Context<Self>,
20509 ) {
20510 self.transact(window, cx, |this, _, cx| {
20511 this.buffer
20512 .read(cx)
20513 .as_singleton()
20514 .expect("you can only call set_text on editors for singleton buffers")
20515 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20516 });
20517 }
20518
20519 pub fn display_text(&self, cx: &mut App) -> String {
20520 self.display_map
20521 .update(cx, |map, cx| map.snapshot(cx))
20522 .text()
20523 }
20524
20525 fn create_minimap(
20526 &self,
20527 minimap_settings: MinimapSettings,
20528 window: &mut Window,
20529 cx: &mut Context<Self>,
20530 ) -> Option<Entity<Self>> {
20531 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20532 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20533 }
20534
20535 fn initialize_new_minimap(
20536 &self,
20537 minimap_settings: MinimapSettings,
20538 window: &mut Window,
20539 cx: &mut Context<Self>,
20540 ) -> Entity<Self> {
20541 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20542 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20543
20544 let mut minimap = Editor::new_internal(
20545 EditorMode::Minimap {
20546 parent: cx.weak_entity(),
20547 },
20548 self.buffer.clone(),
20549 None,
20550 Some(self.display_map.clone()),
20551 window,
20552 cx,
20553 );
20554 minimap.scroll_manager.clone_state(&self.scroll_manager);
20555 minimap.set_text_style_refinement(TextStyleRefinement {
20556 font_size: Some(MINIMAP_FONT_SIZE),
20557 font_weight: Some(MINIMAP_FONT_WEIGHT),
20558 font_family: Some(MINIMAP_FONT_FAMILY),
20559 ..Default::default()
20560 });
20561 minimap.update_minimap_configuration(minimap_settings, cx);
20562 cx.new(|_| minimap)
20563 }
20564
20565 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20566 let current_line_highlight = minimap_settings
20567 .current_line_highlight
20568 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20569 self.set_current_line_highlight(Some(current_line_highlight));
20570 }
20571
20572 pub fn minimap(&self) -> Option<&Entity<Self>> {
20573 self.minimap
20574 .as_ref()
20575 .filter(|_| self.minimap_visibility.visible())
20576 }
20577
20578 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20579 let mut wrap_guides = smallvec![];
20580
20581 if self.show_wrap_guides == Some(false) {
20582 return wrap_guides;
20583 }
20584
20585 let settings = self.buffer.read(cx).language_settings(cx);
20586 if settings.show_wrap_guides {
20587 match self.soft_wrap_mode(cx) {
20588 SoftWrap::Column(soft_wrap) => {
20589 wrap_guides.push((soft_wrap as usize, true));
20590 }
20591 SoftWrap::Bounded(soft_wrap) => {
20592 wrap_guides.push((soft_wrap as usize, true));
20593 }
20594 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20595 }
20596 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20597 }
20598
20599 wrap_guides
20600 }
20601
20602 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20603 let settings = self.buffer.read(cx).language_settings(cx);
20604 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20605 match mode {
20606 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20607 SoftWrap::None
20608 }
20609 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20610 language_settings::SoftWrap::PreferredLineLength => {
20611 SoftWrap::Column(settings.preferred_line_length)
20612 }
20613 language_settings::SoftWrap::Bounded => {
20614 SoftWrap::Bounded(settings.preferred_line_length)
20615 }
20616 }
20617 }
20618
20619 pub fn set_soft_wrap_mode(
20620 &mut self,
20621 mode: language_settings::SoftWrap,
20622
20623 cx: &mut Context<Self>,
20624 ) {
20625 self.soft_wrap_mode_override = Some(mode);
20626 cx.notify();
20627 }
20628
20629 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20630 self.hard_wrap = hard_wrap;
20631 cx.notify();
20632 }
20633
20634 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20635 self.text_style_refinement = Some(style);
20636 }
20637
20638 /// called by the Element so we know what style we were most recently rendered with.
20639 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20640 // We intentionally do not inform the display map about the minimap style
20641 // so that wrapping is not recalculated and stays consistent for the editor
20642 // and its linked minimap.
20643 if !self.mode.is_minimap() {
20644 let font = style.text.font();
20645 let font_size = style.text.font_size.to_pixels(window.rem_size());
20646 let display_map = self
20647 .placeholder_display_map
20648 .as_ref()
20649 .filter(|_| self.is_empty(cx))
20650 .unwrap_or(&self.display_map);
20651
20652 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20653 }
20654 self.style = Some(style);
20655 }
20656
20657 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20658 if self.style.is_none() {
20659 self.style = Some(self.create_style(cx));
20660 }
20661 self.style.as_ref().unwrap()
20662 }
20663
20664 // Called by the element. This method is not designed to be called outside of the editor
20665 // element's layout code because it does not notify when rewrapping is computed synchronously.
20666 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20667 if self.is_empty(cx) {
20668 self.placeholder_display_map
20669 .as_ref()
20670 .map_or(false, |display_map| {
20671 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20672 })
20673 } else {
20674 self.display_map
20675 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20676 }
20677 }
20678
20679 pub fn set_soft_wrap(&mut self) {
20680 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20681 }
20682
20683 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20684 if self.soft_wrap_mode_override.is_some() {
20685 self.soft_wrap_mode_override.take();
20686 } else {
20687 let soft_wrap = match self.soft_wrap_mode(cx) {
20688 SoftWrap::GitDiff => return,
20689 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20690 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20691 language_settings::SoftWrap::None
20692 }
20693 };
20694 self.soft_wrap_mode_override = Some(soft_wrap);
20695 }
20696 cx.notify();
20697 }
20698
20699 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20700 let Some(workspace) = self.workspace() else {
20701 return;
20702 };
20703 let fs = workspace.read(cx).app_state().fs.clone();
20704 let current_show = TabBarSettings::get_global(cx).show;
20705 update_settings_file(fs, cx, move |setting, _| {
20706 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20707 });
20708 }
20709
20710 pub fn toggle_indent_guides(
20711 &mut self,
20712 _: &ToggleIndentGuides,
20713 _: &mut Window,
20714 cx: &mut Context<Self>,
20715 ) {
20716 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20717 self.buffer
20718 .read(cx)
20719 .language_settings(cx)
20720 .indent_guides
20721 .enabled
20722 });
20723 self.show_indent_guides = Some(!currently_enabled);
20724 cx.notify();
20725 }
20726
20727 fn should_show_indent_guides(&self) -> Option<bool> {
20728 self.show_indent_guides
20729 }
20730
20731 pub fn disable_indent_guides_for_buffer(
20732 &mut self,
20733 buffer_id: BufferId,
20734 cx: &mut Context<Self>,
20735 ) {
20736 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20737 cx.notify();
20738 }
20739
20740 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20741 self.buffers_with_disabled_indent_guides
20742 .contains(&buffer_id)
20743 }
20744
20745 pub fn toggle_line_numbers(
20746 &mut self,
20747 _: &ToggleLineNumbers,
20748 _: &mut Window,
20749 cx: &mut Context<Self>,
20750 ) {
20751 let mut editor_settings = EditorSettings::get_global(cx).clone();
20752 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20753 EditorSettings::override_global(editor_settings, cx);
20754 }
20755
20756 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20757 if let Some(show_line_numbers) = self.show_line_numbers {
20758 return show_line_numbers;
20759 }
20760 EditorSettings::get_global(cx).gutter.line_numbers
20761 }
20762
20763 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20764 match (
20765 self.use_relative_line_numbers,
20766 EditorSettings::get_global(cx).relative_line_numbers,
20767 ) {
20768 (None, setting) => setting,
20769 (Some(false), _) => RelativeLineNumbers::Disabled,
20770 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20771 (Some(true), _) => RelativeLineNumbers::Enabled,
20772 }
20773 }
20774
20775 pub fn toggle_relative_line_numbers(
20776 &mut self,
20777 _: &ToggleRelativeLineNumbers,
20778 _: &mut Window,
20779 cx: &mut Context<Self>,
20780 ) {
20781 let is_relative = self.relative_line_numbers(cx);
20782 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20783 }
20784
20785 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20786 self.use_relative_line_numbers = is_relative;
20787 cx.notify();
20788 }
20789
20790 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20791 self.show_gutter = show_gutter;
20792 cx.notify();
20793 }
20794
20795 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20796 self.show_scrollbars = ScrollbarAxes {
20797 horizontal: show,
20798 vertical: show,
20799 };
20800 cx.notify();
20801 }
20802
20803 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20804 self.show_scrollbars.vertical = show;
20805 cx.notify();
20806 }
20807
20808 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20809 self.show_scrollbars.horizontal = show;
20810 cx.notify();
20811 }
20812
20813 pub fn set_minimap_visibility(
20814 &mut self,
20815 minimap_visibility: MinimapVisibility,
20816 window: &mut Window,
20817 cx: &mut Context<Self>,
20818 ) {
20819 if self.minimap_visibility != minimap_visibility {
20820 if minimap_visibility.visible() && self.minimap.is_none() {
20821 let minimap_settings = EditorSettings::get_global(cx).minimap;
20822 self.minimap =
20823 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20824 }
20825 self.minimap_visibility = minimap_visibility;
20826 cx.notify();
20827 }
20828 }
20829
20830 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20831 self.set_show_scrollbars(false, cx);
20832 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20833 }
20834
20835 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20836 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20837 }
20838
20839 /// Normally the text in full mode and auto height editors is padded on the
20840 /// left side by roughly half a character width for improved hit testing.
20841 ///
20842 /// Use this method to disable this for cases where this is not wanted (e.g.
20843 /// if you want to align the editor text with some other text above or below)
20844 /// or if you want to add this padding to single-line editors.
20845 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20846 self.offset_content = offset_content;
20847 cx.notify();
20848 }
20849
20850 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20851 self.show_line_numbers = Some(show_line_numbers);
20852 cx.notify();
20853 }
20854
20855 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20856 self.disable_expand_excerpt_buttons = true;
20857 cx.notify();
20858 }
20859
20860 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
20861 self.delegate_expand_excerpts = delegate;
20862 }
20863
20864 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20865 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20866 cx.notify();
20867 }
20868
20869 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20870 self.show_code_actions = Some(show_code_actions);
20871 cx.notify();
20872 }
20873
20874 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20875 self.show_runnables = Some(show_runnables);
20876 cx.notify();
20877 }
20878
20879 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20880 self.show_breakpoints = Some(show_breakpoints);
20881 cx.notify();
20882 }
20883
20884 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
20885 self.show_diff_review_button = show;
20886 cx.notify();
20887 }
20888
20889 pub fn show_diff_review_button(&self) -> bool {
20890 self.show_diff_review_button
20891 }
20892
20893 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20894 if self.display_map.read(cx).masked != masked {
20895 self.display_map.update(cx, |map, _| map.masked = masked);
20896 }
20897 cx.notify()
20898 }
20899
20900 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20901 self.show_wrap_guides = Some(show_wrap_guides);
20902 cx.notify();
20903 }
20904
20905 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20906 self.show_indent_guides = Some(show_indent_guides);
20907 cx.notify();
20908 }
20909
20910 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20911 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20912 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20913 && let Some(dir) = file.abs_path(cx).parent()
20914 {
20915 return Some(dir.to_owned());
20916 }
20917 }
20918
20919 None
20920 }
20921
20922 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20923 self.active_excerpt(cx)?
20924 .1
20925 .read(cx)
20926 .file()
20927 .and_then(|f| f.as_local())
20928 }
20929
20930 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20931 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20932 let buffer = buffer.read(cx);
20933 if let Some(project_path) = buffer.project_path(cx) {
20934 let project = self.project()?.read(cx);
20935 project.absolute_path(&project_path, cx)
20936 } else {
20937 buffer
20938 .file()
20939 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20940 }
20941 })
20942 }
20943
20944 pub fn reveal_in_finder(
20945 &mut self,
20946 _: &RevealInFileManager,
20947 _window: &mut Window,
20948 cx: &mut Context<Self>,
20949 ) {
20950 if let Some(target) = self.target_file(cx) {
20951 cx.reveal_path(&target.abs_path(cx));
20952 }
20953 }
20954
20955 pub fn copy_path(
20956 &mut self,
20957 _: &zed_actions::workspace::CopyPath,
20958 _window: &mut Window,
20959 cx: &mut Context<Self>,
20960 ) {
20961 if let Some(path) = self.target_file_abs_path(cx)
20962 && let Some(path) = path.to_str()
20963 {
20964 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20965 } else {
20966 cx.propagate();
20967 }
20968 }
20969
20970 pub fn copy_relative_path(
20971 &mut self,
20972 _: &zed_actions::workspace::CopyRelativePath,
20973 _window: &mut Window,
20974 cx: &mut Context<Self>,
20975 ) {
20976 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20977 let project = self.project()?.read(cx);
20978 let path = buffer.read(cx).file()?.path();
20979 let path = path.display(project.path_style(cx));
20980 Some(path)
20981 }) {
20982 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20983 } else {
20984 cx.propagate();
20985 }
20986 }
20987
20988 /// Returns the project path for the editor's buffer, if any buffer is
20989 /// opened in the editor.
20990 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20991 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20992 buffer.read(cx).project_path(cx)
20993 } else {
20994 None
20995 }
20996 }
20997
20998 // Returns true if the editor handled a go-to-line request
20999 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
21000 maybe!({
21001 let breakpoint_store = self.breakpoint_store.as_ref()?;
21002
21003 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
21004 else {
21005 self.clear_row_highlights::<ActiveDebugLine>();
21006 return None;
21007 };
21008
21009 let position = active_stack_frame.position;
21010 let buffer_id = position.buffer_id?;
21011 let snapshot = self
21012 .project
21013 .as_ref()?
21014 .read(cx)
21015 .buffer_for_id(buffer_id, cx)?
21016 .read(cx)
21017 .snapshot();
21018
21019 let mut handled = false;
21020 for (id, ExcerptRange { context, .. }) in
21021 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
21022 {
21023 if context.start.cmp(&position, &snapshot).is_ge()
21024 || context.end.cmp(&position, &snapshot).is_lt()
21025 {
21026 continue;
21027 }
21028 let snapshot = self.buffer.read(cx).snapshot(cx);
21029 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
21030
21031 handled = true;
21032 self.clear_row_highlights::<ActiveDebugLine>();
21033
21034 self.go_to_line::<ActiveDebugLine>(
21035 multibuffer_anchor,
21036 Some(cx.theme().colors().editor_debugger_active_line_background),
21037 window,
21038 cx,
21039 );
21040
21041 cx.notify();
21042 }
21043
21044 handled.then_some(())
21045 })
21046 .is_some()
21047 }
21048
21049 pub fn copy_file_name_without_extension(
21050 &mut self,
21051 _: &CopyFileNameWithoutExtension,
21052 _: &mut Window,
21053 cx: &mut Context<Self>,
21054 ) {
21055 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21056 let file = buffer.read(cx).file()?;
21057 file.path().file_stem()
21058 }) {
21059 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
21060 }
21061 }
21062
21063 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
21064 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21065 let file = buffer.read(cx).file()?;
21066 Some(file.file_name(cx))
21067 }) {
21068 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
21069 }
21070 }
21071
21072 pub fn toggle_git_blame(
21073 &mut self,
21074 _: &::git::Blame,
21075 window: &mut Window,
21076 cx: &mut Context<Self>,
21077 ) {
21078 self.show_git_blame_gutter = !self.show_git_blame_gutter;
21079
21080 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
21081 self.start_git_blame(true, window, cx);
21082 }
21083
21084 cx.notify();
21085 }
21086
21087 pub fn toggle_git_blame_inline(
21088 &mut self,
21089 _: &ToggleGitBlameInline,
21090 window: &mut Window,
21091 cx: &mut Context<Self>,
21092 ) {
21093 self.toggle_git_blame_inline_internal(true, window, cx);
21094 cx.notify();
21095 }
21096
21097 pub fn open_git_blame_commit(
21098 &mut self,
21099 _: &OpenGitBlameCommit,
21100 window: &mut Window,
21101 cx: &mut Context<Self>,
21102 ) {
21103 self.open_git_blame_commit_internal(window, cx);
21104 }
21105
21106 fn open_git_blame_commit_internal(
21107 &mut self,
21108 window: &mut Window,
21109 cx: &mut Context<Self>,
21110 ) -> Option<()> {
21111 let blame = self.blame.as_ref()?;
21112 let snapshot = self.snapshot(window, cx);
21113 let cursor = self
21114 .selections
21115 .newest::<Point>(&snapshot.display_snapshot)
21116 .head();
21117 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
21118 let (_, blame_entry) = blame
21119 .update(cx, |blame, cx| {
21120 blame
21121 .blame_for_rows(
21122 &[RowInfo {
21123 buffer_id: Some(buffer.remote_id()),
21124 buffer_row: Some(point.row),
21125 ..Default::default()
21126 }],
21127 cx,
21128 )
21129 .next()
21130 })
21131 .flatten()?;
21132 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21133 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
21134 let workspace = self.workspace()?.downgrade();
21135 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
21136 None
21137 }
21138
21139 pub fn git_blame_inline_enabled(&self) -> bool {
21140 self.git_blame_inline_enabled
21141 }
21142
21143 pub fn toggle_selection_menu(
21144 &mut self,
21145 _: &ToggleSelectionMenu,
21146 _: &mut Window,
21147 cx: &mut Context<Self>,
21148 ) {
21149 self.show_selection_menu = self
21150 .show_selection_menu
21151 .map(|show_selections_menu| !show_selections_menu)
21152 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
21153
21154 cx.notify();
21155 }
21156
21157 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
21158 self.show_selection_menu
21159 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
21160 }
21161
21162 fn start_git_blame(
21163 &mut self,
21164 user_triggered: bool,
21165 window: &mut Window,
21166 cx: &mut Context<Self>,
21167 ) {
21168 if let Some(project) = self.project() {
21169 if let Some(buffer) = self.buffer().read(cx).as_singleton()
21170 && buffer.read(cx).file().is_none()
21171 {
21172 return;
21173 }
21174
21175 let focused = self.focus_handle(cx).contains_focused(window, cx);
21176
21177 let project = project.clone();
21178 let blame = cx
21179 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
21180 self.blame_subscription =
21181 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
21182 self.blame = Some(blame);
21183 }
21184 }
21185
21186 fn toggle_git_blame_inline_internal(
21187 &mut self,
21188 user_triggered: bool,
21189 window: &mut Window,
21190 cx: &mut Context<Self>,
21191 ) {
21192 if self.git_blame_inline_enabled {
21193 self.git_blame_inline_enabled = false;
21194 self.show_git_blame_inline = false;
21195 self.show_git_blame_inline_delay_task.take();
21196 } else {
21197 self.git_blame_inline_enabled = true;
21198 self.start_git_blame_inline(user_triggered, window, cx);
21199 }
21200
21201 cx.notify();
21202 }
21203
21204 fn start_git_blame_inline(
21205 &mut self,
21206 user_triggered: bool,
21207 window: &mut Window,
21208 cx: &mut Context<Self>,
21209 ) {
21210 self.start_git_blame(user_triggered, window, cx);
21211
21212 if ProjectSettings::get_global(cx)
21213 .git
21214 .inline_blame_delay()
21215 .is_some()
21216 {
21217 self.start_inline_blame_timer(window, cx);
21218 } else {
21219 self.show_git_blame_inline = true
21220 }
21221 }
21222
21223 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
21224 self.blame.as_ref()
21225 }
21226
21227 pub fn show_git_blame_gutter(&self) -> bool {
21228 self.show_git_blame_gutter
21229 }
21230
21231 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
21232 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
21233 }
21234
21235 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
21236 self.show_git_blame_inline
21237 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
21238 && !self.newest_selection_head_on_empty_line(cx)
21239 && self.has_blame_entries(cx)
21240 }
21241
21242 fn has_blame_entries(&self, cx: &App) -> bool {
21243 self.blame()
21244 .is_some_and(|blame| blame.read(cx).has_generated_entries())
21245 }
21246
21247 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
21248 let cursor_anchor = self.selections.newest_anchor().head();
21249
21250 let snapshot = self.buffer.read(cx).snapshot(cx);
21251 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
21252
21253 snapshot.line_len(buffer_row) == 0
21254 }
21255
21256 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
21257 let buffer_and_selection = maybe!({
21258 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21259 let selection_range = selection.range();
21260
21261 let multi_buffer = self.buffer().read(cx);
21262 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21263 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21264
21265 let (buffer, range, _) = if selection.reversed {
21266 buffer_ranges.first()
21267 } else {
21268 buffer_ranges.last()
21269 }?;
21270
21271 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21272 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21273
21274 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21275 let selection = start_row_in_buffer..end_row_in_buffer;
21276
21277 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21278 };
21279
21280 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21281
21282 Some((
21283 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21284 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, Bias::Left, buffer)
21285 ..buffer_diff_snapshot.row_to_base_text_row(
21286 end_row_in_buffer,
21287 Bias::Left,
21288 buffer,
21289 ),
21290 ))
21291 });
21292
21293 let Some((buffer, selection)) = buffer_and_selection else {
21294 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21295 };
21296
21297 let Some(project) = self.project() else {
21298 return Task::ready(Err(anyhow!("editor does not have project")));
21299 };
21300
21301 project.update(cx, |project, cx| {
21302 project.get_permalink_to_line(&buffer, selection, cx)
21303 })
21304 }
21305
21306 pub fn copy_permalink_to_line(
21307 &mut self,
21308 _: &CopyPermalinkToLine,
21309 window: &mut Window,
21310 cx: &mut Context<Self>,
21311 ) {
21312 let permalink_task = self.get_permalink_to_line(cx);
21313 let workspace = self.workspace();
21314
21315 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21316 Ok(permalink) => {
21317 cx.update(|_, cx| {
21318 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21319 })
21320 .ok();
21321 }
21322 Err(err) => {
21323 let message = format!("Failed to copy permalink: {err}");
21324
21325 anyhow::Result::<()>::Err(err).log_err();
21326
21327 if let Some(workspace) = workspace {
21328 workspace
21329 .update_in(cx, |workspace, _, cx| {
21330 struct CopyPermalinkToLine;
21331
21332 workspace.show_toast(
21333 Toast::new(
21334 NotificationId::unique::<CopyPermalinkToLine>(),
21335 message,
21336 ),
21337 cx,
21338 )
21339 })
21340 .ok();
21341 }
21342 }
21343 })
21344 .detach();
21345 }
21346
21347 pub fn copy_file_location(
21348 &mut self,
21349 _: &CopyFileLocation,
21350 _: &mut Window,
21351 cx: &mut Context<Self>,
21352 ) {
21353 let selection = self
21354 .selections
21355 .newest::<Point>(&self.display_snapshot(cx))
21356 .start
21357 .row
21358 + 1;
21359 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21360 let project = self.project()?.read(cx);
21361 let file = buffer.read(cx).file()?;
21362 let path = file.path().display(project.path_style(cx));
21363
21364 Some(format!("{path}:{selection}"))
21365 }) {
21366 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21367 }
21368 }
21369
21370 pub fn open_permalink_to_line(
21371 &mut self,
21372 _: &OpenPermalinkToLine,
21373 window: &mut Window,
21374 cx: &mut Context<Self>,
21375 ) {
21376 let permalink_task = self.get_permalink_to_line(cx);
21377 let workspace = self.workspace();
21378
21379 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21380 Ok(permalink) => {
21381 cx.update(|_, cx| {
21382 cx.open_url(permalink.as_ref());
21383 })
21384 .ok();
21385 }
21386 Err(err) => {
21387 let message = format!("Failed to open permalink: {err}");
21388
21389 anyhow::Result::<()>::Err(err).log_err();
21390
21391 if let Some(workspace) = workspace {
21392 workspace.update(cx, |workspace, cx| {
21393 struct OpenPermalinkToLine;
21394
21395 workspace.show_toast(
21396 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
21397 cx,
21398 )
21399 });
21400 }
21401 }
21402 })
21403 .detach();
21404 }
21405
21406 pub fn insert_uuid_v4(
21407 &mut self,
21408 _: &InsertUuidV4,
21409 window: &mut Window,
21410 cx: &mut Context<Self>,
21411 ) {
21412 self.insert_uuid(UuidVersion::V4, window, cx);
21413 }
21414
21415 pub fn insert_uuid_v7(
21416 &mut self,
21417 _: &InsertUuidV7,
21418 window: &mut Window,
21419 cx: &mut Context<Self>,
21420 ) {
21421 self.insert_uuid(UuidVersion::V7, window, cx);
21422 }
21423
21424 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21425 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21426 self.transact(window, cx, |this, window, cx| {
21427 let edits = this
21428 .selections
21429 .all::<Point>(&this.display_snapshot(cx))
21430 .into_iter()
21431 .map(|selection| {
21432 let uuid = match version {
21433 UuidVersion::V4 => uuid::Uuid::new_v4(),
21434 UuidVersion::V7 => uuid::Uuid::now_v7(),
21435 };
21436
21437 (selection.range(), uuid.to_string())
21438 });
21439 this.edit(edits, cx);
21440 this.refresh_edit_prediction(true, false, window, cx);
21441 });
21442 }
21443
21444 pub fn open_selections_in_multibuffer(
21445 &mut self,
21446 _: &OpenSelectionsInMultibuffer,
21447 window: &mut Window,
21448 cx: &mut Context<Self>,
21449 ) {
21450 let multibuffer = self.buffer.read(cx);
21451
21452 let Some(buffer) = multibuffer.as_singleton() else {
21453 return;
21454 };
21455
21456 let Some(workspace) = self.workspace() else {
21457 return;
21458 };
21459
21460 let title = multibuffer.title(cx).to_string();
21461
21462 let locations = self
21463 .selections
21464 .all_anchors(&self.display_snapshot(cx))
21465 .iter()
21466 .map(|selection| {
21467 (
21468 buffer.clone(),
21469 (selection.start.text_anchor..selection.end.text_anchor)
21470 .to_point(buffer.read(cx)),
21471 )
21472 })
21473 .into_group_map();
21474
21475 cx.spawn_in(window, async move |_, cx| {
21476 workspace.update_in(cx, |workspace, window, cx| {
21477 Self::open_locations_in_multibuffer(
21478 workspace,
21479 locations,
21480 format!("Selections for '{title}'"),
21481 false,
21482 false,
21483 MultibufferSelectionMode::All,
21484 window,
21485 cx,
21486 );
21487 })
21488 })
21489 .detach();
21490 }
21491
21492 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21493 /// last highlight added will be used.
21494 ///
21495 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21496 pub fn highlight_rows<T: 'static>(
21497 &mut self,
21498 range: Range<Anchor>,
21499 color: Hsla,
21500 options: RowHighlightOptions,
21501 cx: &mut Context<Self>,
21502 ) {
21503 let snapshot = self.buffer().read(cx).snapshot(cx);
21504 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21505 let ix = row_highlights.binary_search_by(|highlight| {
21506 Ordering::Equal
21507 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21508 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21509 });
21510
21511 if let Err(mut ix) = ix {
21512 let index = post_inc(&mut self.highlight_order);
21513
21514 // If this range intersects with the preceding highlight, then merge it with
21515 // the preceding highlight. Otherwise insert a new highlight.
21516 let mut merged = false;
21517 if ix > 0 {
21518 let prev_highlight = &mut row_highlights[ix - 1];
21519 if prev_highlight
21520 .range
21521 .end
21522 .cmp(&range.start, &snapshot)
21523 .is_ge()
21524 {
21525 ix -= 1;
21526 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21527 prev_highlight.range.end = range.end;
21528 }
21529 merged = true;
21530 prev_highlight.index = index;
21531 prev_highlight.color = color;
21532 prev_highlight.options = options;
21533 }
21534 }
21535
21536 if !merged {
21537 row_highlights.insert(
21538 ix,
21539 RowHighlight {
21540 range,
21541 index,
21542 color,
21543 options,
21544 type_id: TypeId::of::<T>(),
21545 },
21546 );
21547 }
21548
21549 // If any of the following highlights intersect with this one, merge them.
21550 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21551 let highlight = &row_highlights[ix];
21552 if next_highlight
21553 .range
21554 .start
21555 .cmp(&highlight.range.end, &snapshot)
21556 .is_le()
21557 {
21558 if next_highlight
21559 .range
21560 .end
21561 .cmp(&highlight.range.end, &snapshot)
21562 .is_gt()
21563 {
21564 row_highlights[ix].range.end = next_highlight.range.end;
21565 }
21566 row_highlights.remove(ix + 1);
21567 } else {
21568 break;
21569 }
21570 }
21571 }
21572 }
21573
21574 /// Remove any highlighted row ranges of the given type that intersect the
21575 /// given ranges.
21576 pub fn remove_highlighted_rows<T: 'static>(
21577 &mut self,
21578 ranges_to_remove: Vec<Range<Anchor>>,
21579 cx: &mut Context<Self>,
21580 ) {
21581 let snapshot = self.buffer().read(cx).snapshot(cx);
21582 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21583 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21584 row_highlights.retain(|highlight| {
21585 while let Some(range_to_remove) = ranges_to_remove.peek() {
21586 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21587 Ordering::Less | Ordering::Equal => {
21588 ranges_to_remove.next();
21589 }
21590 Ordering::Greater => {
21591 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21592 Ordering::Less | Ordering::Equal => {
21593 return false;
21594 }
21595 Ordering::Greater => break,
21596 }
21597 }
21598 }
21599 }
21600
21601 true
21602 })
21603 }
21604
21605 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21606 pub fn clear_row_highlights<T: 'static>(&mut self) {
21607 self.highlighted_rows.remove(&TypeId::of::<T>());
21608 }
21609
21610 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21611 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21612 self.highlighted_rows
21613 .get(&TypeId::of::<T>())
21614 .map_or(&[] as &[_], |vec| vec.as_slice())
21615 .iter()
21616 .map(|highlight| (highlight.range.clone(), highlight.color))
21617 }
21618
21619 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21620 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21621 /// Allows to ignore certain kinds of highlights.
21622 pub fn highlighted_display_rows(
21623 &self,
21624 window: &mut Window,
21625 cx: &mut App,
21626 ) -> BTreeMap<DisplayRow, LineHighlight> {
21627 let snapshot = self.snapshot(window, cx);
21628 let mut used_highlight_orders = HashMap::default();
21629 self.highlighted_rows
21630 .iter()
21631 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21632 .fold(
21633 BTreeMap::<DisplayRow, LineHighlight>::new(),
21634 |mut unique_rows, highlight| {
21635 let start = highlight.range.start.to_display_point(&snapshot);
21636 let end = highlight.range.end.to_display_point(&snapshot);
21637 let start_row = start.row().0;
21638 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21639 {
21640 end.row().0.saturating_sub(1)
21641 } else {
21642 end.row().0
21643 };
21644 for row in start_row..=end_row {
21645 let used_index =
21646 used_highlight_orders.entry(row).or_insert(highlight.index);
21647 if highlight.index >= *used_index {
21648 *used_index = highlight.index;
21649 unique_rows.insert(
21650 DisplayRow(row),
21651 LineHighlight {
21652 include_gutter: highlight.options.include_gutter,
21653 border: None,
21654 background: highlight.color.into(),
21655 type_id: Some(highlight.type_id),
21656 },
21657 );
21658 }
21659 }
21660 unique_rows
21661 },
21662 )
21663 }
21664
21665 pub fn highlighted_display_row_for_autoscroll(
21666 &self,
21667 snapshot: &DisplaySnapshot,
21668 ) -> Option<DisplayRow> {
21669 self.highlighted_rows
21670 .values()
21671 .flat_map(|highlighted_rows| highlighted_rows.iter())
21672 .filter_map(|highlight| {
21673 if highlight.options.autoscroll {
21674 Some(highlight.range.start.to_display_point(snapshot).row())
21675 } else {
21676 None
21677 }
21678 })
21679 .min()
21680 }
21681
21682 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21683 self.highlight_background::<SearchWithinRange>(
21684 ranges,
21685 |_, colors| colors.colors().editor_document_highlight_read_background,
21686 cx,
21687 )
21688 }
21689
21690 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21691 self.breadcrumb_header = Some(new_header);
21692 }
21693
21694 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21695 self.clear_background_highlights::<SearchWithinRange>(cx);
21696 }
21697
21698 pub fn highlight_background<T: 'static>(
21699 &mut self,
21700 ranges: &[Range<Anchor>],
21701 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21702 cx: &mut Context<Self>,
21703 ) {
21704 self.background_highlights.insert(
21705 HighlightKey::Type(TypeId::of::<T>()),
21706 (Arc::new(color_fetcher), Arc::from(ranges)),
21707 );
21708 self.scrollbar_marker_state.dirty = true;
21709 cx.notify();
21710 }
21711
21712 pub fn highlight_background_key<T: 'static>(
21713 &mut self,
21714 key: usize,
21715 ranges: &[Range<Anchor>],
21716 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21717 cx: &mut Context<Self>,
21718 ) {
21719 self.background_highlights.insert(
21720 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21721 (Arc::new(color_fetcher), Arc::from(ranges)),
21722 );
21723 self.scrollbar_marker_state.dirty = true;
21724 cx.notify();
21725 }
21726
21727 pub fn clear_background_highlights<T: 'static>(
21728 &mut self,
21729 cx: &mut Context<Self>,
21730 ) -> Option<BackgroundHighlight> {
21731 let text_highlights = self
21732 .background_highlights
21733 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21734 if !text_highlights.1.is_empty() {
21735 self.scrollbar_marker_state.dirty = true;
21736 cx.notify();
21737 }
21738 Some(text_highlights)
21739 }
21740
21741 pub fn highlight_gutter<T: 'static>(
21742 &mut self,
21743 ranges: impl Into<Vec<Range<Anchor>>>,
21744 color_fetcher: fn(&App) -> Hsla,
21745 cx: &mut Context<Self>,
21746 ) {
21747 self.gutter_highlights
21748 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21749 cx.notify();
21750 }
21751
21752 pub fn clear_gutter_highlights<T: 'static>(
21753 &mut self,
21754 cx: &mut Context<Self>,
21755 ) -> Option<GutterHighlight> {
21756 cx.notify();
21757 self.gutter_highlights.remove(&TypeId::of::<T>())
21758 }
21759
21760 pub fn insert_gutter_highlight<T: 'static>(
21761 &mut self,
21762 range: Range<Anchor>,
21763 color_fetcher: fn(&App) -> Hsla,
21764 cx: &mut Context<Self>,
21765 ) {
21766 let snapshot = self.buffer().read(cx).snapshot(cx);
21767 let mut highlights = self
21768 .gutter_highlights
21769 .remove(&TypeId::of::<T>())
21770 .map(|(_, highlights)| highlights)
21771 .unwrap_or_default();
21772 let ix = highlights.binary_search_by(|highlight| {
21773 Ordering::Equal
21774 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21775 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21776 });
21777 if let Err(ix) = ix {
21778 highlights.insert(ix, range);
21779 }
21780 self.gutter_highlights
21781 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21782 }
21783
21784 pub fn remove_gutter_highlights<T: 'static>(
21785 &mut self,
21786 ranges_to_remove: Vec<Range<Anchor>>,
21787 cx: &mut Context<Self>,
21788 ) {
21789 let snapshot = self.buffer().read(cx).snapshot(cx);
21790 let Some((color_fetcher, mut gutter_highlights)) =
21791 self.gutter_highlights.remove(&TypeId::of::<T>())
21792 else {
21793 return;
21794 };
21795 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21796 gutter_highlights.retain(|highlight| {
21797 while let Some(range_to_remove) = ranges_to_remove.peek() {
21798 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21799 Ordering::Less | Ordering::Equal => {
21800 ranges_to_remove.next();
21801 }
21802 Ordering::Greater => {
21803 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21804 Ordering::Less | Ordering::Equal => {
21805 return false;
21806 }
21807 Ordering::Greater => break,
21808 }
21809 }
21810 }
21811 }
21812
21813 true
21814 });
21815 self.gutter_highlights
21816 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21817 }
21818
21819 #[cfg(feature = "test-support")]
21820 pub fn all_text_highlights(
21821 &self,
21822 window: &mut Window,
21823 cx: &mut Context<Self>,
21824 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21825 let snapshot = self.snapshot(window, cx);
21826 self.display_map.update(cx, |display_map, _| {
21827 display_map
21828 .all_text_highlights()
21829 .map(|highlight| {
21830 let (style, ranges) = highlight.as_ref();
21831 (
21832 *style,
21833 ranges
21834 .iter()
21835 .map(|range| range.clone().to_display_points(&snapshot))
21836 .collect(),
21837 )
21838 })
21839 .collect()
21840 })
21841 }
21842
21843 #[cfg(feature = "test-support")]
21844 pub fn all_text_background_highlights(
21845 &self,
21846 window: &mut Window,
21847 cx: &mut Context<Self>,
21848 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21849 let snapshot = self.snapshot(window, cx);
21850 let buffer = &snapshot.buffer_snapshot();
21851 let start = buffer.anchor_before(MultiBufferOffset(0));
21852 let end = buffer.anchor_after(buffer.len());
21853 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21854 }
21855
21856 #[cfg(any(test, feature = "test-support"))]
21857 pub fn sorted_background_highlights_in_range(
21858 &self,
21859 search_range: Range<Anchor>,
21860 display_snapshot: &DisplaySnapshot,
21861 theme: &Theme,
21862 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21863 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21864 res.sort_by(|a, b| {
21865 a.0.start
21866 .cmp(&b.0.start)
21867 .then_with(|| a.0.end.cmp(&b.0.end))
21868 .then_with(|| a.1.cmp(&b.1))
21869 });
21870 res
21871 }
21872
21873 #[cfg(feature = "test-support")]
21874 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21875 let snapshot = self.buffer().read(cx).snapshot(cx);
21876
21877 let highlights = self
21878 .background_highlights
21879 .get(&HighlightKey::Type(TypeId::of::<
21880 items::BufferSearchHighlights,
21881 >()));
21882
21883 if let Some((_color, ranges)) = highlights {
21884 ranges
21885 .iter()
21886 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21887 .collect_vec()
21888 } else {
21889 vec![]
21890 }
21891 }
21892
21893 fn document_highlights_for_position<'a>(
21894 &'a self,
21895 position: Anchor,
21896 buffer: &'a MultiBufferSnapshot,
21897 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21898 let read_highlights = self
21899 .background_highlights
21900 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21901 .map(|h| &h.1);
21902 let write_highlights = self
21903 .background_highlights
21904 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21905 .map(|h| &h.1);
21906 let left_position = position.bias_left(buffer);
21907 let right_position = position.bias_right(buffer);
21908 read_highlights
21909 .into_iter()
21910 .chain(write_highlights)
21911 .flat_map(move |ranges| {
21912 let start_ix = match ranges.binary_search_by(|probe| {
21913 let cmp = probe.end.cmp(&left_position, buffer);
21914 if cmp.is_ge() {
21915 Ordering::Greater
21916 } else {
21917 Ordering::Less
21918 }
21919 }) {
21920 Ok(i) | Err(i) => i,
21921 };
21922
21923 ranges[start_ix..]
21924 .iter()
21925 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21926 })
21927 }
21928
21929 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21930 self.background_highlights
21931 .get(&HighlightKey::Type(TypeId::of::<T>()))
21932 .is_some_and(|(_, highlights)| !highlights.is_empty())
21933 }
21934
21935 /// Returns all background highlights for a given range.
21936 ///
21937 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21938 pub fn background_highlights_in_range(
21939 &self,
21940 search_range: Range<Anchor>,
21941 display_snapshot: &DisplaySnapshot,
21942 theme: &Theme,
21943 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21944 let mut results = Vec::new();
21945 for (color_fetcher, ranges) in self.background_highlights.values() {
21946 let start_ix = match ranges.binary_search_by(|probe| {
21947 let cmp = probe
21948 .end
21949 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21950 if cmp.is_gt() {
21951 Ordering::Greater
21952 } else {
21953 Ordering::Less
21954 }
21955 }) {
21956 Ok(i) | Err(i) => i,
21957 };
21958 for (index, range) in ranges[start_ix..].iter().enumerate() {
21959 if range
21960 .start
21961 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21962 .is_ge()
21963 {
21964 break;
21965 }
21966
21967 let color = color_fetcher(&(start_ix + index), theme);
21968 let start = range.start.to_display_point(display_snapshot);
21969 let end = range.end.to_display_point(display_snapshot);
21970 results.push((start..end, color))
21971 }
21972 }
21973 results
21974 }
21975
21976 pub fn gutter_highlights_in_range(
21977 &self,
21978 search_range: Range<Anchor>,
21979 display_snapshot: &DisplaySnapshot,
21980 cx: &App,
21981 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21982 let mut results = Vec::new();
21983 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21984 let color = color_fetcher(cx);
21985 let start_ix = match ranges.binary_search_by(|probe| {
21986 let cmp = probe
21987 .end
21988 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21989 if cmp.is_gt() {
21990 Ordering::Greater
21991 } else {
21992 Ordering::Less
21993 }
21994 }) {
21995 Ok(i) | Err(i) => i,
21996 };
21997 for range in &ranges[start_ix..] {
21998 if range
21999 .start
22000 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
22001 .is_ge()
22002 {
22003 break;
22004 }
22005
22006 let start = range.start.to_display_point(display_snapshot);
22007 let end = range.end.to_display_point(display_snapshot);
22008 results.push((start..end, color))
22009 }
22010 }
22011 results
22012 }
22013
22014 /// Get the text ranges corresponding to the redaction query
22015 pub fn redacted_ranges(
22016 &self,
22017 search_range: Range<Anchor>,
22018 display_snapshot: &DisplaySnapshot,
22019 cx: &App,
22020 ) -> Vec<Range<DisplayPoint>> {
22021 display_snapshot
22022 .buffer_snapshot()
22023 .redacted_ranges(search_range, |file| {
22024 if let Some(file) = file {
22025 file.is_private()
22026 && EditorSettings::get(
22027 Some(SettingsLocation {
22028 worktree_id: file.worktree_id(cx),
22029 path: file.path().as_ref(),
22030 }),
22031 cx,
22032 )
22033 .redact_private_values
22034 } else {
22035 false
22036 }
22037 })
22038 .map(|range| {
22039 range.start.to_display_point(display_snapshot)
22040 ..range.end.to_display_point(display_snapshot)
22041 })
22042 .collect()
22043 }
22044
22045 pub fn highlight_text_key<T: 'static>(
22046 &mut self,
22047 key: usize,
22048 ranges: Vec<Range<Anchor>>,
22049 style: HighlightStyle,
22050 merge: bool,
22051 cx: &mut Context<Self>,
22052 ) {
22053 self.display_map.update(cx, |map, cx| {
22054 map.highlight_text(
22055 HighlightKey::TypePlus(TypeId::of::<T>(), key),
22056 ranges,
22057 style,
22058 merge,
22059 cx,
22060 );
22061 });
22062 cx.notify();
22063 }
22064
22065 pub fn highlight_text<T: 'static>(
22066 &mut self,
22067 ranges: Vec<Range<Anchor>>,
22068 style: HighlightStyle,
22069 cx: &mut Context<Self>,
22070 ) {
22071 self.display_map.update(cx, |map, cx| {
22072 map.highlight_text(
22073 HighlightKey::Type(TypeId::of::<T>()),
22074 ranges,
22075 style,
22076 false,
22077 cx,
22078 )
22079 });
22080 cx.notify();
22081 }
22082
22083 pub fn text_highlights<'a, T: 'static>(
22084 &'a self,
22085 cx: &'a App,
22086 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
22087 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
22088 }
22089
22090 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
22091 let cleared = self
22092 .display_map
22093 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
22094 if cleared {
22095 cx.notify();
22096 }
22097 }
22098
22099 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
22100 (self.read_only(cx) || self.blink_manager.read(cx).visible())
22101 && self.focus_handle.is_focused(window)
22102 }
22103
22104 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
22105 self.show_cursor_when_unfocused = is_enabled;
22106 cx.notify();
22107 }
22108
22109 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
22110 cx.notify();
22111 }
22112
22113 fn on_debug_session_event(
22114 &mut self,
22115 _session: Entity<Session>,
22116 event: &SessionEvent,
22117 cx: &mut Context<Self>,
22118 ) {
22119 if let SessionEvent::InvalidateInlineValue = event {
22120 self.refresh_inline_values(cx);
22121 }
22122 }
22123
22124 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
22125 let Some(project) = self.project.clone() else {
22126 return;
22127 };
22128
22129 if !self.inline_value_cache.enabled {
22130 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
22131 self.splice_inlays(&inlays, Vec::new(), cx);
22132 return;
22133 }
22134
22135 let current_execution_position = self
22136 .highlighted_rows
22137 .get(&TypeId::of::<ActiveDebugLine>())
22138 .and_then(|lines| lines.last().map(|line| line.range.end));
22139
22140 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
22141 let inline_values = editor
22142 .update(cx, |editor, cx| {
22143 let Some(current_execution_position) = current_execution_position else {
22144 return Some(Task::ready(Ok(Vec::new())));
22145 };
22146
22147 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
22148 let snapshot = buffer.snapshot(cx);
22149
22150 let excerpt = snapshot.excerpt_containing(
22151 current_execution_position..current_execution_position,
22152 )?;
22153
22154 editor.buffer.read(cx).buffer(excerpt.buffer_id())
22155 })?;
22156
22157 let range =
22158 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
22159
22160 project.inline_values(buffer, range, cx)
22161 })
22162 .ok()
22163 .flatten()?
22164 .await
22165 .context("refreshing debugger inlays")
22166 .log_err()?;
22167
22168 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
22169
22170 for (buffer_id, inline_value) in inline_values
22171 .into_iter()
22172 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
22173 {
22174 buffer_inline_values
22175 .entry(buffer_id)
22176 .or_default()
22177 .push(inline_value);
22178 }
22179
22180 editor
22181 .update(cx, |editor, cx| {
22182 let snapshot = editor.buffer.read(cx).snapshot(cx);
22183 let mut new_inlays = Vec::default();
22184
22185 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
22186 let buffer_id = buffer_snapshot.remote_id();
22187 buffer_inline_values
22188 .get(&buffer_id)
22189 .into_iter()
22190 .flatten()
22191 .for_each(|hint| {
22192 let inlay = Inlay::debugger(
22193 post_inc(&mut editor.next_inlay_id),
22194 Anchor::in_buffer(excerpt_id, hint.position),
22195 hint.text(),
22196 );
22197 if !inlay.text().chars().contains(&'\n') {
22198 new_inlays.push(inlay);
22199 }
22200 });
22201 }
22202
22203 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
22204 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
22205
22206 editor.splice_inlays(&inlay_ids, new_inlays, cx);
22207 })
22208 .ok()?;
22209 Some(())
22210 });
22211 }
22212
22213 fn on_buffer_event(
22214 &mut self,
22215 multibuffer: &Entity<MultiBuffer>,
22216 event: &multi_buffer::Event,
22217 window: &mut Window,
22218 cx: &mut Context<Self>,
22219 ) {
22220 match event {
22221 multi_buffer::Event::Edited { edited_buffer } => {
22222 self.scrollbar_marker_state.dirty = true;
22223 self.active_indent_guides_state.dirty = true;
22224 self.refresh_active_diagnostics(cx);
22225 self.refresh_code_actions(window, cx);
22226 self.refresh_single_line_folds(window, cx);
22227 self.refresh_matching_bracket_highlights(window, cx);
22228 if self.has_active_edit_prediction() {
22229 self.update_visible_edit_prediction(window, cx);
22230 }
22231
22232 if let Some(buffer) = edited_buffer {
22233 if buffer.read(cx).file().is_none() {
22234 cx.emit(EditorEvent::TitleChanged);
22235 }
22236
22237 if self.project.is_some() {
22238 let buffer_id = buffer.read(cx).remote_id();
22239 self.register_buffer(buffer_id, cx);
22240 self.update_lsp_data(Some(buffer_id), window, cx);
22241 self.refresh_inlay_hints(
22242 InlayHintRefreshReason::BufferEdited(buffer_id),
22243 cx,
22244 );
22245 }
22246 }
22247
22248 cx.emit(EditorEvent::BufferEdited);
22249 cx.emit(SearchEvent::MatchesInvalidated);
22250
22251 let Some(project) = &self.project else { return };
22252 let (telemetry, is_via_ssh) = {
22253 let project = project.read(cx);
22254 let telemetry = project.client().telemetry().clone();
22255 let is_via_ssh = project.is_via_remote_server();
22256 (telemetry, is_via_ssh)
22257 };
22258 telemetry.log_edit_event("editor", is_via_ssh);
22259 }
22260 multi_buffer::Event::ExcerptsAdded {
22261 buffer,
22262 predecessor,
22263 excerpts,
22264 } => {
22265 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22266 let buffer_id = buffer.read(cx).remote_id();
22267 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22268 && let Some(project) = &self.project
22269 {
22270 update_uncommitted_diff_for_buffer(
22271 cx.entity(),
22272 project,
22273 [buffer.clone()],
22274 self.buffer.clone(),
22275 cx,
22276 )
22277 .detach();
22278 }
22279 self.update_lsp_data(Some(buffer_id), window, cx);
22280 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22281 self.colorize_brackets(false, cx);
22282 self.refresh_selected_text_highlights(true, window, cx);
22283 cx.emit(EditorEvent::ExcerptsAdded {
22284 buffer: buffer.clone(),
22285 predecessor: *predecessor,
22286 excerpts: excerpts.clone(),
22287 });
22288 }
22289 multi_buffer::Event::ExcerptsRemoved {
22290 ids,
22291 removed_buffer_ids,
22292 } => {
22293 if let Some(inlay_hints) = &mut self.inlay_hints {
22294 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22295 }
22296 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22297 for buffer_id in removed_buffer_ids {
22298 self.registered_buffers.remove(buffer_id);
22299 }
22300 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22301 cx.emit(EditorEvent::ExcerptsRemoved {
22302 ids: ids.clone(),
22303 removed_buffer_ids: removed_buffer_ids.clone(),
22304 });
22305 }
22306 multi_buffer::Event::ExcerptsEdited {
22307 excerpt_ids,
22308 buffer_ids,
22309 } => {
22310 self.display_map.update(cx, |map, cx| {
22311 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22312 });
22313 cx.emit(EditorEvent::ExcerptsEdited {
22314 ids: excerpt_ids.clone(),
22315 });
22316 }
22317 multi_buffer::Event::ExcerptsExpanded { ids } => {
22318 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22319 self.refresh_document_highlights(cx);
22320 for id in ids {
22321 self.fetched_tree_sitter_chunks.remove(id);
22322 }
22323 self.colorize_brackets(false, cx);
22324 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22325 }
22326 multi_buffer::Event::Reparsed(buffer_id) => {
22327 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22328 self.refresh_selected_text_highlights(true, window, cx);
22329 self.colorize_brackets(true, cx);
22330 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22331
22332 cx.emit(EditorEvent::Reparsed(*buffer_id));
22333 }
22334 multi_buffer::Event::DiffHunksToggled => {
22335 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22336 }
22337 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22338 if !is_fresh_language {
22339 self.registered_buffers.remove(&buffer_id);
22340 }
22341 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22342 cx.emit(EditorEvent::Reparsed(*buffer_id));
22343 cx.notify();
22344 }
22345 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22346 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22347 multi_buffer::Event::FileHandleChanged
22348 | multi_buffer::Event::Reloaded
22349 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22350 multi_buffer::Event::DiagnosticsUpdated => {
22351 self.update_diagnostics_state(window, cx);
22352 }
22353 _ => {}
22354 };
22355 }
22356
22357 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22358 if !self.diagnostics_enabled() {
22359 return;
22360 }
22361 self.refresh_active_diagnostics(cx);
22362 self.refresh_inline_diagnostics(true, window, cx);
22363 self.scrollbar_marker_state.dirty = true;
22364 cx.notify();
22365 }
22366
22367 pub fn start_temporary_diff_override(&mut self) {
22368 self.load_diff_task.take();
22369 self.temporary_diff_override = true;
22370 }
22371
22372 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22373 self.temporary_diff_override = false;
22374 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22375 self.buffer.update(cx, |buffer, cx| {
22376 buffer.set_all_diff_hunks_collapsed(cx);
22377 });
22378
22379 if let Some(project) = self.project.clone() {
22380 self.load_diff_task = Some(
22381 update_uncommitted_diff_for_buffer(
22382 cx.entity(),
22383 &project,
22384 self.buffer.read(cx).all_buffers(),
22385 self.buffer.clone(),
22386 cx,
22387 )
22388 .shared(),
22389 );
22390 }
22391 }
22392
22393 fn on_display_map_changed(
22394 &mut self,
22395 _: Entity<DisplayMap>,
22396 _: &mut Window,
22397 cx: &mut Context<Self>,
22398 ) {
22399 cx.notify();
22400 }
22401
22402 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22403 if !self.mode.is_full() {
22404 return None;
22405 }
22406
22407 let theme_settings = theme::ThemeSettings::get_global(cx);
22408 let theme = cx.theme();
22409 let accent_colors = theme.accents().clone();
22410
22411 let accent_overrides = theme_settings
22412 .theme_overrides
22413 .get(theme.name.as_ref())
22414 .map(|theme_style| &theme_style.accents)
22415 .into_iter()
22416 .flatten()
22417 .chain(
22418 theme_settings
22419 .experimental_theme_overrides
22420 .as_ref()
22421 .map(|overrides| &overrides.accents)
22422 .into_iter()
22423 .flatten(),
22424 )
22425 .flat_map(|accent| accent.0.clone())
22426 .collect();
22427
22428 Some(AccentData {
22429 colors: accent_colors,
22430 overrides: accent_overrides,
22431 })
22432 }
22433
22434 fn fetch_applicable_language_settings(
22435 &self,
22436 cx: &App,
22437 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22438 if !self.mode.is_full() {
22439 return HashMap::default();
22440 }
22441
22442 self.buffer().read(cx).all_buffers().into_iter().fold(
22443 HashMap::default(),
22444 |mut acc, buffer| {
22445 let buffer = buffer.read(cx);
22446 let language = buffer.language().map(|language| language.name());
22447 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22448 let file = buffer.file();
22449 v.insert(language_settings(language, file, cx).into_owned());
22450 }
22451 acc
22452 },
22453 )
22454 }
22455
22456 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22457 let new_language_settings = self.fetch_applicable_language_settings(cx);
22458 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22459 self.applicable_language_settings = new_language_settings;
22460
22461 let new_accents = self.fetch_accent_data(cx);
22462 let accents_changed = new_accents != self.accent_data;
22463 self.accent_data = new_accents;
22464
22465 if self.diagnostics_enabled() {
22466 let new_severity = EditorSettings::get_global(cx)
22467 .diagnostics_max_severity
22468 .unwrap_or(DiagnosticSeverity::Hint);
22469 self.set_max_diagnostics_severity(new_severity, cx);
22470 }
22471 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22472 self.update_edit_prediction_settings(cx);
22473 self.refresh_edit_prediction(true, false, window, cx);
22474 self.refresh_inline_values(cx);
22475 self.refresh_inlay_hints(
22476 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22477 self.selections.newest_anchor().head(),
22478 &self.buffer.read(cx).snapshot(cx),
22479 cx,
22480 )),
22481 cx,
22482 );
22483
22484 let old_cursor_shape = self.cursor_shape;
22485 let old_show_breadcrumbs = self.show_breadcrumbs;
22486
22487 {
22488 let editor_settings = EditorSettings::get_global(cx);
22489 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22490 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22491 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22492 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22493 }
22494
22495 if old_cursor_shape != self.cursor_shape {
22496 cx.emit(EditorEvent::CursorShapeChanged);
22497 }
22498
22499 if old_show_breadcrumbs != self.show_breadcrumbs {
22500 cx.emit(EditorEvent::BreadcrumbsChanged);
22501 }
22502
22503 let project_settings = ProjectSettings::get_global(cx);
22504 self.buffer_serialization = self
22505 .should_serialize_buffer()
22506 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22507
22508 if self.mode.is_full() {
22509 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22510 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22511 if self.show_inline_diagnostics != show_inline_diagnostics {
22512 self.show_inline_diagnostics = show_inline_diagnostics;
22513 self.refresh_inline_diagnostics(false, window, cx);
22514 }
22515
22516 if self.git_blame_inline_enabled != inline_blame_enabled {
22517 self.toggle_git_blame_inline_internal(false, window, cx);
22518 }
22519
22520 let minimap_settings = EditorSettings::get_global(cx).minimap;
22521 if self.minimap_visibility != MinimapVisibility::Disabled {
22522 if self.minimap_visibility.settings_visibility()
22523 != minimap_settings.minimap_enabled()
22524 {
22525 self.set_minimap_visibility(
22526 MinimapVisibility::for_mode(self.mode(), cx),
22527 window,
22528 cx,
22529 );
22530 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22531 minimap_entity.update(cx, |minimap_editor, cx| {
22532 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22533 })
22534 }
22535 }
22536
22537 if language_settings_changed || accents_changed {
22538 self.colorize_brackets(true, cx);
22539 }
22540
22541 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22542 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22543 }) {
22544 if !inlay_splice.is_empty() {
22545 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22546 }
22547 self.refresh_colors_for_visible_range(None, window, cx);
22548 }
22549 }
22550
22551 cx.notify();
22552 }
22553
22554 pub fn set_searchable(&mut self, searchable: bool) {
22555 self.searchable = searchable;
22556 }
22557
22558 pub fn searchable(&self) -> bool {
22559 self.searchable
22560 }
22561
22562 pub fn open_excerpts_in_split(
22563 &mut self,
22564 _: &OpenExcerptsSplit,
22565 window: &mut Window,
22566 cx: &mut Context<Self>,
22567 ) {
22568 self.open_excerpts_common(None, true, window, cx)
22569 }
22570
22571 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22572 self.open_excerpts_common(None, false, window, cx)
22573 }
22574
22575 fn open_excerpts_common(
22576 &mut self,
22577 jump_data: Option<JumpData>,
22578 split: bool,
22579 window: &mut Window,
22580 cx: &mut Context<Self>,
22581 ) {
22582 let Some(workspace) = self.workspace() else {
22583 cx.propagate();
22584 return;
22585 };
22586
22587 if self.buffer.read(cx).is_singleton() {
22588 cx.propagate();
22589 return;
22590 }
22591
22592 let mut new_selections_by_buffer = HashMap::default();
22593 match &jump_data {
22594 Some(JumpData::MultiBufferPoint {
22595 excerpt_id,
22596 position,
22597 anchor,
22598 line_offset_from_top,
22599 }) => {
22600 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22601 if let Some(buffer) = multi_buffer_snapshot
22602 .buffer_id_for_excerpt(*excerpt_id)
22603 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22604 {
22605 let buffer_snapshot = buffer.read(cx).snapshot();
22606 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22607 language::ToPoint::to_point(anchor, &buffer_snapshot)
22608 } else {
22609 buffer_snapshot.clip_point(*position, Bias::Left)
22610 };
22611 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22612 new_selections_by_buffer.insert(
22613 buffer,
22614 (
22615 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22616 Some(*line_offset_from_top),
22617 ),
22618 );
22619 }
22620 }
22621 Some(JumpData::MultiBufferRow {
22622 row,
22623 line_offset_from_top,
22624 }) => {
22625 let point = MultiBufferPoint::new(row.0, 0);
22626 if let Some((buffer, buffer_point, _)) =
22627 self.buffer.read(cx).point_to_buffer_point(point, cx)
22628 {
22629 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22630 new_selections_by_buffer
22631 .entry(buffer)
22632 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22633 .0
22634 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22635 }
22636 }
22637 None => {
22638 let selections = self
22639 .selections
22640 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22641 let multi_buffer = self.buffer.read(cx);
22642 for selection in selections {
22643 for (snapshot, range, _, anchor) in multi_buffer
22644 .snapshot(cx)
22645 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22646 {
22647 if let Some(anchor) = anchor {
22648 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22649 else {
22650 continue;
22651 };
22652 let offset = text::ToOffset::to_offset(
22653 &anchor.text_anchor,
22654 &buffer_handle.read(cx).snapshot(),
22655 );
22656 let range = BufferOffset(offset)..BufferOffset(offset);
22657 new_selections_by_buffer
22658 .entry(buffer_handle)
22659 .or_insert((Vec::new(), None))
22660 .0
22661 .push(range)
22662 } else {
22663 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22664 else {
22665 continue;
22666 };
22667 new_selections_by_buffer
22668 .entry(buffer_handle)
22669 .or_insert((Vec::new(), None))
22670 .0
22671 .push(range)
22672 }
22673 }
22674 }
22675 }
22676 }
22677
22678 new_selections_by_buffer
22679 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
22680
22681 if new_selections_by_buffer.is_empty() {
22682 return;
22683 }
22684
22685 // We defer the pane interaction because we ourselves are a workspace item
22686 // and activating a new item causes the pane to call a method on us reentrantly,
22687 // which panics if we're on the stack.
22688 window.defer(cx, move |window, cx| {
22689 workspace.update(cx, |workspace, cx| {
22690 let pane = if split {
22691 workspace.adjacent_pane(window, cx)
22692 } else {
22693 workspace.active_pane().clone()
22694 };
22695
22696 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22697 let buffer_read = buffer.read(cx);
22698 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22699 (true, project::File::from_dyn(Some(file)).is_some())
22700 } else {
22701 (false, false)
22702 };
22703
22704 // If project file is none workspace.open_project_item will fail to open the excerpt
22705 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22706 // so we check if there's a tab match in that case first
22707 let editor = (!has_file || !is_project_file)
22708 .then(|| {
22709 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22710 // so `workspace.open_project_item` will never find them, always opening a new editor.
22711 // Instead, we try to activate the existing editor in the pane first.
22712 let (editor, pane_item_index, pane_item_id) =
22713 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22714 let editor = item.downcast::<Editor>()?;
22715 let singleton_buffer =
22716 editor.read(cx).buffer().read(cx).as_singleton()?;
22717 if singleton_buffer == buffer {
22718 Some((editor, i, item.item_id()))
22719 } else {
22720 None
22721 }
22722 })?;
22723 pane.update(cx, |pane, cx| {
22724 pane.activate_item(pane_item_index, true, true, window, cx);
22725 if !PreviewTabsSettings::get_global(cx)
22726 .enable_preview_from_multibuffer
22727 {
22728 pane.unpreview_item_if_preview(pane_item_id);
22729 }
22730 });
22731 Some(editor)
22732 })
22733 .flatten()
22734 .unwrap_or_else(|| {
22735 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22736 .enable_keep_preview_on_code_navigation;
22737 let allow_new_preview =
22738 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22739 workspace.open_project_item::<Self>(
22740 pane.clone(),
22741 buffer,
22742 true,
22743 true,
22744 keep_old_preview,
22745 allow_new_preview,
22746 window,
22747 cx,
22748 )
22749 });
22750
22751 editor.update(cx, |editor, cx| {
22752 if has_file && !is_project_file {
22753 editor.set_read_only(true);
22754 }
22755 let autoscroll = match scroll_offset {
22756 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22757 None => Autoscroll::newest(),
22758 };
22759 let nav_history = editor.nav_history.take();
22760 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22761 let Some((&excerpt_id, _, buffer_snapshot)) =
22762 multibuffer_snapshot.as_singleton()
22763 else {
22764 return;
22765 };
22766 editor.change_selections(
22767 SelectionEffects::scroll(autoscroll),
22768 window,
22769 cx,
22770 |s| {
22771 s.select_ranges(ranges.into_iter().map(|range| {
22772 let range = buffer_snapshot.anchor_before(range.start)
22773 ..buffer_snapshot.anchor_after(range.end);
22774 multibuffer_snapshot
22775 .anchor_range_in_excerpt(excerpt_id, range)
22776 .unwrap()
22777 }));
22778 },
22779 );
22780 editor.nav_history = nav_history;
22781 });
22782 }
22783 })
22784 });
22785 }
22786
22787 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22788 let snapshot = self.buffer.read(cx).read(cx);
22789 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22790 Some(
22791 ranges
22792 .iter()
22793 .map(move |range| {
22794 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22795 })
22796 .collect(),
22797 )
22798 }
22799
22800 fn selection_replacement_ranges(
22801 &self,
22802 range: Range<MultiBufferOffsetUtf16>,
22803 cx: &mut App,
22804 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22805 let selections = self
22806 .selections
22807 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22808 let newest_selection = selections
22809 .iter()
22810 .max_by_key(|selection| selection.id)
22811 .unwrap();
22812 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22813 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22814 let snapshot = self.buffer.read(cx).read(cx);
22815 selections
22816 .into_iter()
22817 .map(|mut selection| {
22818 selection.start.0.0 =
22819 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22820 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22821 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22822 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22823 })
22824 .collect()
22825 }
22826
22827 fn report_editor_event(
22828 &self,
22829 reported_event: ReportEditorEvent,
22830 file_extension: Option<String>,
22831 cx: &App,
22832 ) {
22833 if cfg!(any(test, feature = "test-support")) {
22834 return;
22835 }
22836
22837 let Some(project) = &self.project else { return };
22838
22839 // If None, we are in a file without an extension
22840 let file = self
22841 .buffer
22842 .read(cx)
22843 .as_singleton()
22844 .and_then(|b| b.read(cx).file());
22845 let file_extension = file_extension.or(file
22846 .as_ref()
22847 .and_then(|file| Path::new(file.file_name(cx)).extension())
22848 .and_then(|e| e.to_str())
22849 .map(|a| a.to_string()));
22850
22851 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22852 .map(|vim_mode| vim_mode.0)
22853 .unwrap_or(false);
22854
22855 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22856 let copilot_enabled = edit_predictions_provider
22857 == language::language_settings::EditPredictionProvider::Copilot;
22858 let copilot_enabled_for_language = self
22859 .buffer
22860 .read(cx)
22861 .language_settings(cx)
22862 .show_edit_predictions;
22863
22864 let project = project.read(cx);
22865 let event_type = reported_event.event_type();
22866
22867 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22868 telemetry::event!(
22869 event_type,
22870 type = if auto_saved {"autosave"} else {"manual"},
22871 file_extension,
22872 vim_mode,
22873 copilot_enabled,
22874 copilot_enabled_for_language,
22875 edit_predictions_provider,
22876 is_via_ssh = project.is_via_remote_server(),
22877 );
22878 } else {
22879 telemetry::event!(
22880 event_type,
22881 file_extension,
22882 vim_mode,
22883 copilot_enabled,
22884 copilot_enabled_for_language,
22885 edit_predictions_provider,
22886 is_via_ssh = project.is_via_remote_server(),
22887 );
22888 };
22889 }
22890
22891 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22892 /// with each line being an array of {text, highlight} objects.
22893 fn copy_highlight_json(
22894 &mut self,
22895 _: &CopyHighlightJson,
22896 window: &mut Window,
22897 cx: &mut Context<Self>,
22898 ) {
22899 #[derive(Serialize)]
22900 struct Chunk<'a> {
22901 text: String,
22902 highlight: Option<&'a str>,
22903 }
22904
22905 let snapshot = self.buffer.read(cx).snapshot(cx);
22906 let range = self
22907 .selected_text_range(false, window, cx)
22908 .and_then(|selection| {
22909 if selection.range.is_empty() {
22910 None
22911 } else {
22912 Some(
22913 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22914 selection.range.start,
22915 )))
22916 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22917 selection.range.end,
22918 ))),
22919 )
22920 }
22921 })
22922 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22923
22924 let chunks = snapshot.chunks(range, true);
22925 let mut lines = Vec::new();
22926 let mut line: VecDeque<Chunk> = VecDeque::new();
22927
22928 let Some(style) = self.style.as_ref() else {
22929 return;
22930 };
22931
22932 for chunk in chunks {
22933 let highlight = chunk
22934 .syntax_highlight_id
22935 .and_then(|id| id.name(&style.syntax));
22936 let mut chunk_lines = chunk.text.split('\n').peekable();
22937 while let Some(text) = chunk_lines.next() {
22938 let mut merged_with_last_token = false;
22939 if let Some(last_token) = line.back_mut()
22940 && last_token.highlight == highlight
22941 {
22942 last_token.text.push_str(text);
22943 merged_with_last_token = true;
22944 }
22945
22946 if !merged_with_last_token {
22947 line.push_back(Chunk {
22948 text: text.into(),
22949 highlight,
22950 });
22951 }
22952
22953 if chunk_lines.peek().is_some() {
22954 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22955 line.pop_front();
22956 }
22957 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22958 line.pop_back();
22959 }
22960
22961 lines.push(mem::take(&mut line));
22962 }
22963 }
22964 }
22965
22966 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22967 return;
22968 };
22969 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22970 }
22971
22972 pub fn open_context_menu(
22973 &mut self,
22974 _: &OpenContextMenu,
22975 window: &mut Window,
22976 cx: &mut Context<Self>,
22977 ) {
22978 self.request_autoscroll(Autoscroll::newest(), cx);
22979 let position = self
22980 .selections
22981 .newest_display(&self.display_snapshot(cx))
22982 .start;
22983 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22984 }
22985
22986 pub fn replay_insert_event(
22987 &mut self,
22988 text: &str,
22989 relative_utf16_range: Option<Range<isize>>,
22990 window: &mut Window,
22991 cx: &mut Context<Self>,
22992 ) {
22993 if !self.input_enabled {
22994 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22995 return;
22996 }
22997 if let Some(relative_utf16_range) = relative_utf16_range {
22998 let selections = self
22999 .selections
23000 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
23001 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23002 let new_ranges = selections.into_iter().map(|range| {
23003 let start = MultiBufferOffsetUtf16(OffsetUtf16(
23004 range
23005 .head()
23006 .0
23007 .0
23008 .saturating_add_signed(relative_utf16_range.start),
23009 ));
23010 let end = MultiBufferOffsetUtf16(OffsetUtf16(
23011 range
23012 .head()
23013 .0
23014 .0
23015 .saturating_add_signed(relative_utf16_range.end),
23016 ));
23017 start..end
23018 });
23019 s.select_ranges(new_ranges);
23020 });
23021 }
23022
23023 self.handle_input(text, window, cx);
23024 }
23025
23026 pub fn is_focused(&self, window: &Window) -> bool {
23027 self.focus_handle.is_focused(window)
23028 }
23029
23030 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23031 cx.emit(EditorEvent::Focused);
23032
23033 if let Some(descendant) = self
23034 .last_focused_descendant
23035 .take()
23036 .and_then(|descendant| descendant.upgrade())
23037 {
23038 window.focus(&descendant, cx);
23039 } else {
23040 if let Some(blame) = self.blame.as_ref() {
23041 blame.update(cx, GitBlame::focus)
23042 }
23043
23044 self.blink_manager.update(cx, BlinkManager::enable);
23045 self.show_cursor_names(window, cx);
23046 self.buffer.update(cx, |buffer, cx| {
23047 buffer.finalize_last_transaction(cx);
23048 if self.leader_id.is_none() {
23049 buffer.set_active_selections(
23050 &self.selections.disjoint_anchors_arc(),
23051 self.selections.line_mode(),
23052 self.cursor_shape,
23053 cx,
23054 );
23055 }
23056 });
23057
23058 if let Some(position_map) = self.last_position_map.clone() {
23059 EditorElement::mouse_moved(
23060 self,
23061 &MouseMoveEvent {
23062 position: window.mouse_position(),
23063 pressed_button: None,
23064 modifiers: window.modifiers(),
23065 },
23066 &position_map,
23067 window,
23068 cx,
23069 );
23070 }
23071 }
23072 }
23073
23074 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23075 cx.emit(EditorEvent::FocusedIn)
23076 }
23077
23078 fn handle_focus_out(
23079 &mut self,
23080 event: FocusOutEvent,
23081 _window: &mut Window,
23082 cx: &mut Context<Self>,
23083 ) {
23084 if event.blurred != self.focus_handle {
23085 self.last_focused_descendant = Some(event.blurred);
23086 }
23087 self.selection_drag_state = SelectionDragState::None;
23088 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
23089 }
23090
23091 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23092 self.blink_manager.update(cx, BlinkManager::disable);
23093 self.buffer
23094 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
23095
23096 if let Some(blame) = self.blame.as_ref() {
23097 blame.update(cx, GitBlame::blur)
23098 }
23099 if !self.hover_state.focused(window, cx) {
23100 hide_hover(self, cx);
23101 }
23102 if !self
23103 .context_menu
23104 .borrow()
23105 .as_ref()
23106 .is_some_and(|context_menu| context_menu.focused(window, cx))
23107 {
23108 self.hide_context_menu(window, cx);
23109 }
23110 self.take_active_edit_prediction(cx);
23111 cx.emit(EditorEvent::Blurred);
23112 cx.notify();
23113 }
23114
23115 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23116 let mut pending: String = window
23117 .pending_input_keystrokes()
23118 .into_iter()
23119 .flatten()
23120 .filter_map(|keystroke| keystroke.key_char.clone())
23121 .collect();
23122
23123 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
23124 pending = "".to_string();
23125 }
23126
23127 let existing_pending = self
23128 .text_highlights::<PendingInput>(cx)
23129 .map(|(_, ranges)| ranges.to_vec());
23130 if existing_pending.is_none() && pending.is_empty() {
23131 return;
23132 }
23133 let transaction =
23134 self.transact(window, cx, |this, window, cx| {
23135 let selections = this
23136 .selections
23137 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
23138 let edits = selections
23139 .iter()
23140 .map(|selection| (selection.end..selection.end, pending.clone()));
23141 this.edit(edits, cx);
23142 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23143 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
23144 sel.start + ix * pending.len()..sel.end + ix * pending.len()
23145 }));
23146 });
23147 if let Some(existing_ranges) = existing_pending {
23148 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
23149 this.edit(edits, cx);
23150 }
23151 });
23152
23153 let snapshot = self.snapshot(window, cx);
23154 let ranges = self
23155 .selections
23156 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
23157 .into_iter()
23158 .map(|selection| {
23159 snapshot.buffer_snapshot().anchor_after(selection.end)
23160 ..snapshot
23161 .buffer_snapshot()
23162 .anchor_before(selection.end + pending.len())
23163 })
23164 .collect();
23165
23166 if pending.is_empty() {
23167 self.clear_highlights::<PendingInput>(cx);
23168 } else {
23169 self.highlight_text::<PendingInput>(
23170 ranges,
23171 HighlightStyle {
23172 underline: Some(UnderlineStyle {
23173 thickness: px(1.),
23174 color: None,
23175 wavy: false,
23176 }),
23177 ..Default::default()
23178 },
23179 cx,
23180 );
23181 }
23182
23183 self.ime_transaction = self.ime_transaction.or(transaction);
23184 if let Some(transaction) = self.ime_transaction {
23185 self.buffer.update(cx, |buffer, cx| {
23186 buffer.group_until_transaction(transaction, cx);
23187 });
23188 }
23189
23190 if self.text_highlights::<PendingInput>(cx).is_none() {
23191 self.ime_transaction.take();
23192 }
23193 }
23194
23195 pub fn register_action_renderer(
23196 &mut self,
23197 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
23198 ) -> Subscription {
23199 let id = self.next_editor_action_id.post_inc();
23200 self.editor_actions
23201 .borrow_mut()
23202 .insert(id, Box::new(listener));
23203
23204 let editor_actions = self.editor_actions.clone();
23205 Subscription::new(move || {
23206 editor_actions.borrow_mut().remove(&id);
23207 })
23208 }
23209
23210 pub fn register_action<A: Action>(
23211 &mut self,
23212 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
23213 ) -> Subscription {
23214 let id = self.next_editor_action_id.post_inc();
23215 let listener = Arc::new(listener);
23216 self.editor_actions.borrow_mut().insert(
23217 id,
23218 Box::new(move |_, window, _| {
23219 let listener = listener.clone();
23220 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
23221 let action = action.downcast_ref().unwrap();
23222 if phase == DispatchPhase::Bubble {
23223 listener(action, window, cx)
23224 }
23225 })
23226 }),
23227 );
23228
23229 let editor_actions = self.editor_actions.clone();
23230 Subscription::new(move || {
23231 editor_actions.borrow_mut().remove(&id);
23232 })
23233 }
23234
23235 pub fn file_header_size(&self) -> u32 {
23236 FILE_HEADER_HEIGHT
23237 }
23238
23239 pub fn restore(
23240 &mut self,
23241 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
23242 window: &mut Window,
23243 cx: &mut Context<Self>,
23244 ) {
23245 self.buffer().update(cx, |multi_buffer, cx| {
23246 for (buffer_id, changes) in revert_changes {
23247 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
23248 buffer.update(cx, |buffer, cx| {
23249 buffer.edit(
23250 changes
23251 .into_iter()
23252 .map(|(range, text)| (range, text.to_string())),
23253 None,
23254 cx,
23255 );
23256 });
23257 }
23258 }
23259 });
23260 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23261 selections.refresh()
23262 });
23263 }
23264
23265 pub fn to_pixel_point(
23266 &mut self,
23267 source: multi_buffer::Anchor,
23268 editor_snapshot: &EditorSnapshot,
23269 window: &mut Window,
23270 cx: &App,
23271 ) -> Option<gpui::Point<Pixels>> {
23272 let source_point = source.to_display_point(editor_snapshot);
23273 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23274 }
23275
23276 pub fn display_to_pixel_point(
23277 &mut self,
23278 source: DisplayPoint,
23279 editor_snapshot: &EditorSnapshot,
23280 window: &mut Window,
23281 cx: &App,
23282 ) -> Option<gpui::Point<Pixels>> {
23283 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23284 let text_layout_details = self.text_layout_details(window);
23285 let scroll_top = text_layout_details
23286 .scroll_anchor
23287 .scroll_position(editor_snapshot)
23288 .y;
23289
23290 if source.row().as_f64() < scroll_top.floor() {
23291 return None;
23292 }
23293 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23294 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23295 Some(gpui::Point::new(source_x, source_y))
23296 }
23297
23298 pub fn has_visible_completions_menu(&self) -> bool {
23299 !self.edit_prediction_preview_is_active()
23300 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23301 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23302 })
23303 }
23304
23305 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23306 if self.mode.is_minimap() {
23307 return;
23308 }
23309 self.addons
23310 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23311 }
23312
23313 pub fn unregister_addon<T: Addon>(&mut self) {
23314 self.addons.remove(&std::any::TypeId::of::<T>());
23315 }
23316
23317 pub fn addon<T: Addon>(&self) -> Option<&T> {
23318 let type_id = std::any::TypeId::of::<T>();
23319 self.addons
23320 .get(&type_id)
23321 .and_then(|item| item.to_any().downcast_ref::<T>())
23322 }
23323
23324 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23325 let type_id = std::any::TypeId::of::<T>();
23326 self.addons
23327 .get_mut(&type_id)
23328 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23329 }
23330
23331 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23332 let text_layout_details = self.text_layout_details(window);
23333 let style = &text_layout_details.editor_style;
23334 let font_id = window.text_system().resolve_font(&style.text.font());
23335 let font_size = style.text.font_size.to_pixels(window.rem_size());
23336 let line_height = style.text.line_height_in_pixels(window.rem_size());
23337 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23338 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23339
23340 CharacterDimensions {
23341 em_width,
23342 em_advance,
23343 line_height,
23344 }
23345 }
23346
23347 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23348 self.load_diff_task.clone()
23349 }
23350
23351 fn read_metadata_from_db(
23352 &mut self,
23353 item_id: u64,
23354 workspace_id: WorkspaceId,
23355 window: &mut Window,
23356 cx: &mut Context<Editor>,
23357 ) {
23358 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23359 && !self.mode.is_minimap()
23360 && WorkspaceSettings::get(None, cx).restore_on_startup
23361 != RestoreOnStartupBehavior::EmptyTab
23362 {
23363 let buffer_snapshot = OnceCell::new();
23364
23365 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23366 && !folds.is_empty()
23367 {
23368 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23369 let snapshot_len = snapshot.len().0;
23370
23371 // Helper: search for fingerprint in buffer, return offset if found
23372 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
23373 // Ensure we start at a character boundary (defensive)
23374 let search_start = snapshot
23375 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
23376 .0;
23377 let search_end = snapshot_len.saturating_sub(fingerprint.len());
23378
23379 let mut byte_offset = search_start;
23380 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
23381 if byte_offset > search_end {
23382 break;
23383 }
23384 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
23385 return Some(byte_offset);
23386 }
23387 byte_offset += ch.len_utf8();
23388 }
23389 None
23390 };
23391
23392 // Track search position to handle duplicate fingerprints correctly.
23393 // Folds are stored in document order, so we advance after each match.
23394 let mut search_start = 0usize;
23395
23396 let valid_folds: Vec<_> = folds
23397 .into_iter()
23398 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
23399 // Skip folds without fingerprints (old data before migration)
23400 let sfp = start_fp?;
23401 let efp = end_fp?;
23402 let efp_len = efp.len();
23403
23404 // Fast path: check if fingerprints match at stored offsets
23405 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
23406 let start_matches = stored_start < snapshot_len
23407 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
23408 let efp_check_pos = stored_end.saturating_sub(efp_len);
23409 let end_matches = efp_check_pos >= stored_start
23410 && stored_end <= snapshot_len
23411 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
23412
23413 let (new_start, new_end) = if start_matches && end_matches {
23414 // Offsets unchanged, use stored values
23415 (stored_start, stored_end)
23416 } else if sfp == efp {
23417 // Short fold: identical fingerprints can only match once per search
23418 // Use stored fold length to compute new_end
23419 let new_start = find_fingerprint(&sfp, search_start)?;
23420 let fold_len = stored_end - stored_start;
23421 let new_end = new_start + fold_len;
23422 (new_start, new_end)
23423 } else {
23424 // Slow path: search for fingerprints in buffer
23425 let new_start = find_fingerprint(&sfp, search_start)?;
23426 // Search for end_fp after start, then add efp_len to get actual fold end
23427 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
23428 let new_end = efp_pos + efp_len;
23429 (new_start, new_end)
23430 };
23431
23432 // Advance search position for next fold
23433 search_start = new_end;
23434
23435 // Validate fold makes sense (end must be after start)
23436 if new_end <= new_start {
23437 return None;
23438 }
23439
23440 Some(
23441 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
23442 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
23443 )
23444 })
23445 .collect();
23446
23447 if !valid_folds.is_empty() {
23448 self.fold_ranges(valid_folds, false, window, cx);
23449
23450 // Migrate folds to current entity_id before workspace cleanup runs.
23451 // Entity IDs change between sessions, but workspace cleanup deletes
23452 // old editor rows (cascading to folds) based on current entity IDs.
23453 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
23454 if new_editor_id != item_id {
23455 cx.spawn(async move |_, _| {
23456 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
23457 .await
23458 .log_err();
23459 })
23460 .detach();
23461 }
23462 }
23463 }
23464
23465 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23466 && !selections.is_empty()
23467 {
23468 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23469 // skip adding the initial selection to selection history
23470 self.selection_history.mode = SelectionHistoryMode::Skipping;
23471 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23472 s.select_ranges(selections.into_iter().map(|(start, end)| {
23473 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23474 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23475 }));
23476 });
23477 self.selection_history.mode = SelectionHistoryMode::Normal;
23478 };
23479 }
23480
23481 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23482 }
23483
23484 fn update_lsp_data(
23485 &mut self,
23486 for_buffer: Option<BufferId>,
23487 window: &mut Window,
23488 cx: &mut Context<'_, Self>,
23489 ) {
23490 self.pull_diagnostics(for_buffer, window, cx);
23491 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23492 }
23493
23494 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23495 if self.ignore_lsp_data() {
23496 return;
23497 }
23498 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23499 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23500 }
23501 }
23502
23503 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23504 if self.ignore_lsp_data() {
23505 return;
23506 }
23507
23508 if !self.registered_buffers.contains_key(&buffer_id)
23509 && let Some(project) = self.project.as_ref()
23510 {
23511 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23512 project.update(cx, |project, cx| {
23513 self.registered_buffers.insert(
23514 buffer_id,
23515 project.register_buffer_with_language_servers(&buffer, cx),
23516 );
23517 });
23518 } else {
23519 self.registered_buffers.remove(&buffer_id);
23520 }
23521 }
23522 }
23523
23524 fn ignore_lsp_data(&self) -> bool {
23525 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23526 // skip any LSP updates for it.
23527 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23528 }
23529
23530 fn create_style(&self, cx: &App) -> EditorStyle {
23531 let settings = ThemeSettings::get_global(cx);
23532
23533 let mut text_style = match self.mode {
23534 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23535 color: cx.theme().colors().editor_foreground,
23536 font_family: settings.ui_font.family.clone(),
23537 font_features: settings.ui_font.features.clone(),
23538 font_fallbacks: settings.ui_font.fallbacks.clone(),
23539 font_size: rems(0.875).into(),
23540 font_weight: settings.ui_font.weight,
23541 line_height: relative(settings.buffer_line_height.value()),
23542 ..Default::default()
23543 },
23544 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23545 color: cx.theme().colors().editor_foreground,
23546 font_family: settings.buffer_font.family.clone(),
23547 font_features: settings.buffer_font.features.clone(),
23548 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23549 font_size: settings.buffer_font_size(cx).into(),
23550 font_weight: settings.buffer_font.weight,
23551 line_height: relative(settings.buffer_line_height.value()),
23552 ..Default::default()
23553 },
23554 };
23555 if let Some(text_style_refinement) = &self.text_style_refinement {
23556 text_style.refine(text_style_refinement)
23557 }
23558
23559 let background = match self.mode {
23560 EditorMode::SingleLine => cx.theme().system().transparent,
23561 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23562 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23563 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23564 };
23565
23566 EditorStyle {
23567 background,
23568 border: cx.theme().colors().border,
23569 local_player: cx.theme().players().local(),
23570 text: text_style,
23571 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23572 syntax: cx.theme().syntax().clone(),
23573 status: cx.theme().status().clone(),
23574 inlay_hints_style: make_inlay_hints_style(cx),
23575 edit_prediction_styles: make_suggestion_styles(cx),
23576 unnecessary_code_fade: settings.unnecessary_code_fade,
23577 show_underlines: self.diagnostics_enabled(),
23578 }
23579 }
23580 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
23581 let cursor = self.selections.newest_anchor().head();
23582 let multibuffer = self.buffer().read(cx);
23583 let is_singleton = multibuffer.is_singleton();
23584 let (buffer_id, symbols) = multibuffer
23585 .read(cx)
23586 .symbols_containing(cursor, Some(variant.syntax()))?;
23587 let buffer = multibuffer.buffer(buffer_id)?;
23588
23589 let buffer = buffer.read(cx);
23590 let settings = ThemeSettings::get_global(cx);
23591 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
23592 let mut breadcrumbs = if is_singleton {
23593 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
23594 buffer
23595 .snapshot()
23596 .resolve_file_path(
23597 self.project
23598 .as_ref()
23599 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
23600 .unwrap_or_default(),
23601 cx,
23602 )
23603 .unwrap_or_else(|| {
23604 if multibuffer.is_singleton() {
23605 multibuffer.title(cx).to_string()
23606 } else {
23607 "untitled".to_string()
23608 }
23609 })
23610 });
23611 vec![BreadcrumbText {
23612 text,
23613 highlights: None,
23614 font: Some(settings.buffer_font.clone()),
23615 }]
23616 } else {
23617 vec![]
23618 };
23619
23620 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
23621 text: symbol.text,
23622 highlights: Some(symbol.highlight_ranges),
23623 font: Some(settings.buffer_font.clone()),
23624 }));
23625 Some(breadcrumbs)
23626 }
23627}
23628
23629fn edit_for_markdown_paste<'a>(
23630 buffer: &MultiBufferSnapshot,
23631 range: Range<MultiBufferOffset>,
23632 to_insert: &'a str,
23633 url: Option<url::Url>,
23634) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23635 if url.is_none() {
23636 return (range, Cow::Borrowed(to_insert));
23637 };
23638
23639 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23640
23641 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23642 Cow::Borrowed(to_insert)
23643 } else {
23644 Cow::Owned(format!("[{old_text}]({to_insert})"))
23645 };
23646 (range, new_text)
23647}
23648
23649fn process_completion_for_edit(
23650 completion: &Completion,
23651 intent: CompletionIntent,
23652 buffer: &Entity<Buffer>,
23653 cursor_position: &text::Anchor,
23654 cx: &mut Context<Editor>,
23655) -> CompletionEdit {
23656 let buffer = buffer.read(cx);
23657 let buffer_snapshot = buffer.snapshot();
23658 let (snippet, new_text) = if completion.is_snippet() {
23659 let mut snippet_source = completion.new_text.clone();
23660 // Workaround for typescript language server issues so that methods don't expand within
23661 // strings and functions with type expressions. The previous point is used because the query
23662 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23663 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23664 let previous_point = if previous_point.column > 0 {
23665 cursor_position.to_previous_offset(&buffer_snapshot)
23666 } else {
23667 cursor_position.to_offset(&buffer_snapshot)
23668 };
23669 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23670 && scope.prefers_label_for_snippet_in_completion()
23671 && let Some(label) = completion.label()
23672 && matches!(
23673 completion.kind(),
23674 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23675 )
23676 {
23677 snippet_source = label;
23678 }
23679 match Snippet::parse(&snippet_source).log_err() {
23680 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23681 None => (None, completion.new_text.clone()),
23682 }
23683 } else {
23684 (None, completion.new_text.clone())
23685 };
23686
23687 let mut range_to_replace = {
23688 let replace_range = &completion.replace_range;
23689 if let CompletionSource::Lsp {
23690 insert_range: Some(insert_range),
23691 ..
23692 } = &completion.source
23693 {
23694 debug_assert_eq!(
23695 insert_range.start, replace_range.start,
23696 "insert_range and replace_range should start at the same position"
23697 );
23698 debug_assert!(
23699 insert_range
23700 .start
23701 .cmp(cursor_position, &buffer_snapshot)
23702 .is_le(),
23703 "insert_range should start before or at cursor position"
23704 );
23705 debug_assert!(
23706 replace_range
23707 .start
23708 .cmp(cursor_position, &buffer_snapshot)
23709 .is_le(),
23710 "replace_range should start before or at cursor position"
23711 );
23712
23713 let should_replace = match intent {
23714 CompletionIntent::CompleteWithInsert => false,
23715 CompletionIntent::CompleteWithReplace => true,
23716 CompletionIntent::Complete | CompletionIntent::Compose => {
23717 let insert_mode =
23718 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23719 .completions
23720 .lsp_insert_mode;
23721 match insert_mode {
23722 LspInsertMode::Insert => false,
23723 LspInsertMode::Replace => true,
23724 LspInsertMode::ReplaceSubsequence => {
23725 let mut text_to_replace = buffer.chars_for_range(
23726 buffer.anchor_before(replace_range.start)
23727 ..buffer.anchor_after(replace_range.end),
23728 );
23729 let mut current_needle = text_to_replace.next();
23730 for haystack_ch in completion.label.text.chars() {
23731 if let Some(needle_ch) = current_needle
23732 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23733 {
23734 current_needle = text_to_replace.next();
23735 }
23736 }
23737 current_needle.is_none()
23738 }
23739 LspInsertMode::ReplaceSuffix => {
23740 if replace_range
23741 .end
23742 .cmp(cursor_position, &buffer_snapshot)
23743 .is_gt()
23744 {
23745 let range_after_cursor = *cursor_position..replace_range.end;
23746 let text_after_cursor = buffer
23747 .text_for_range(
23748 buffer.anchor_before(range_after_cursor.start)
23749 ..buffer.anchor_after(range_after_cursor.end),
23750 )
23751 .collect::<String>()
23752 .to_ascii_lowercase();
23753 completion
23754 .label
23755 .text
23756 .to_ascii_lowercase()
23757 .ends_with(&text_after_cursor)
23758 } else {
23759 true
23760 }
23761 }
23762 }
23763 }
23764 };
23765
23766 if should_replace {
23767 replace_range.clone()
23768 } else {
23769 insert_range.clone()
23770 }
23771 } else {
23772 replace_range.clone()
23773 }
23774 };
23775
23776 if range_to_replace
23777 .end
23778 .cmp(cursor_position, &buffer_snapshot)
23779 .is_lt()
23780 {
23781 range_to_replace.end = *cursor_position;
23782 }
23783
23784 let replace_range = range_to_replace.to_offset(buffer);
23785 CompletionEdit {
23786 new_text,
23787 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23788 snippet,
23789 }
23790}
23791
23792struct CompletionEdit {
23793 new_text: String,
23794 replace_range: Range<BufferOffset>,
23795 snippet: Option<Snippet>,
23796}
23797
23798fn comment_delimiter_for_newline(
23799 start_point: &Point,
23800 buffer: &MultiBufferSnapshot,
23801 language: &LanguageScope,
23802) -> Option<Arc<str>> {
23803 let delimiters = language.line_comment_prefixes();
23804 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23805 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23806
23807 let num_of_whitespaces = snapshot
23808 .chars_for_range(range.clone())
23809 .take_while(|c| c.is_whitespace())
23810 .count();
23811 let comment_candidate = snapshot
23812 .chars_for_range(range.clone())
23813 .skip(num_of_whitespaces)
23814 .take(max_len_of_delimiter)
23815 .collect::<String>();
23816 let (delimiter, trimmed_len) = delimiters
23817 .iter()
23818 .filter_map(|delimiter| {
23819 let prefix = delimiter.trim_end();
23820 if comment_candidate.starts_with(prefix) {
23821 Some((delimiter, prefix.len()))
23822 } else {
23823 None
23824 }
23825 })
23826 .max_by_key(|(_, len)| *len)?;
23827
23828 if let Some(BlockCommentConfig {
23829 start: block_start, ..
23830 }) = language.block_comment()
23831 {
23832 let block_start_trimmed = block_start.trim_end();
23833 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23834 let line_content = snapshot
23835 .chars_for_range(range)
23836 .skip(num_of_whitespaces)
23837 .take(block_start_trimmed.len())
23838 .collect::<String>();
23839
23840 if line_content.starts_with(block_start_trimmed) {
23841 return None;
23842 }
23843 }
23844 }
23845
23846 let cursor_is_placed_after_comment_marker =
23847 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23848 if cursor_is_placed_after_comment_marker {
23849 Some(delimiter.clone())
23850 } else {
23851 None
23852 }
23853}
23854
23855fn documentation_delimiter_for_newline(
23856 start_point: &Point,
23857 buffer: &MultiBufferSnapshot,
23858 language: &LanguageScope,
23859 newline_config: &mut NewlineConfig,
23860) -> Option<Arc<str>> {
23861 let BlockCommentConfig {
23862 start: start_tag,
23863 end: end_tag,
23864 prefix: delimiter,
23865 tab_size: len,
23866 } = language.documentation_comment()?;
23867 let is_within_block_comment = buffer
23868 .language_scope_at(*start_point)
23869 .is_some_and(|scope| scope.override_name() == Some("comment"));
23870 if !is_within_block_comment {
23871 return None;
23872 }
23873
23874 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23875
23876 let num_of_whitespaces = snapshot
23877 .chars_for_range(range.clone())
23878 .take_while(|c| c.is_whitespace())
23879 .count();
23880
23881 // 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.
23882 let column = start_point.column;
23883 let cursor_is_after_start_tag = {
23884 let start_tag_len = start_tag.len();
23885 let start_tag_line = snapshot
23886 .chars_for_range(range.clone())
23887 .skip(num_of_whitespaces)
23888 .take(start_tag_len)
23889 .collect::<String>();
23890 if start_tag_line.starts_with(start_tag.as_ref()) {
23891 num_of_whitespaces + start_tag_len <= column as usize
23892 } else {
23893 false
23894 }
23895 };
23896
23897 let cursor_is_after_delimiter = {
23898 let delimiter_trim = delimiter.trim_end();
23899 let delimiter_line = snapshot
23900 .chars_for_range(range.clone())
23901 .skip(num_of_whitespaces)
23902 .take(delimiter_trim.len())
23903 .collect::<String>();
23904 if delimiter_line.starts_with(delimiter_trim) {
23905 num_of_whitespaces + delimiter_trim.len() <= column as usize
23906 } else {
23907 false
23908 }
23909 };
23910
23911 let mut needs_extra_line = false;
23912 let mut extra_line_additional_indent = IndentSize::spaces(0);
23913
23914 let cursor_is_before_end_tag_if_exists = {
23915 let mut char_position = 0u32;
23916 let mut end_tag_offset = None;
23917
23918 'outer: for chunk in snapshot.text_for_range(range) {
23919 if let Some(byte_pos) = chunk.find(&**end_tag) {
23920 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23921 end_tag_offset = Some(char_position + chars_before_match);
23922 break 'outer;
23923 }
23924 char_position += chunk.chars().count() as u32;
23925 }
23926
23927 if let Some(end_tag_offset) = end_tag_offset {
23928 let cursor_is_before_end_tag = column <= end_tag_offset;
23929 if cursor_is_after_start_tag {
23930 if cursor_is_before_end_tag {
23931 needs_extra_line = true;
23932 }
23933 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23934 if cursor_is_at_start_of_end_tag {
23935 extra_line_additional_indent.len = *len;
23936 }
23937 }
23938 cursor_is_before_end_tag
23939 } else {
23940 true
23941 }
23942 };
23943
23944 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23945 && cursor_is_before_end_tag_if_exists
23946 {
23947 let additional_indent = if cursor_is_after_start_tag {
23948 IndentSize::spaces(*len)
23949 } else {
23950 IndentSize::spaces(0)
23951 };
23952
23953 *newline_config = NewlineConfig::Newline {
23954 additional_indent,
23955 extra_line_additional_indent: if needs_extra_line {
23956 Some(extra_line_additional_indent)
23957 } else {
23958 None
23959 },
23960 prevent_auto_indent: true,
23961 };
23962 Some(delimiter.clone())
23963 } else {
23964 None
23965 }
23966}
23967
23968const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
23969
23970fn list_delimiter_for_newline(
23971 start_point: &Point,
23972 buffer: &MultiBufferSnapshot,
23973 language: &LanguageScope,
23974 newline_config: &mut NewlineConfig,
23975) -> Option<Arc<str>> {
23976 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23977
23978 let num_of_whitespaces = snapshot
23979 .chars_for_range(range.clone())
23980 .take_while(|c| c.is_whitespace())
23981 .count();
23982
23983 let task_list_entries: Vec<_> = language
23984 .task_list()
23985 .into_iter()
23986 .flat_map(|config| {
23987 config
23988 .prefixes
23989 .iter()
23990 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
23991 })
23992 .collect();
23993 let unordered_list_entries: Vec<_> = language
23994 .unordered_list()
23995 .iter()
23996 .map(|marker| (marker.as_ref(), marker.as_ref()))
23997 .collect();
23998
23999 let all_entries: Vec<_> = task_list_entries
24000 .into_iter()
24001 .chain(unordered_list_entries)
24002 .collect();
24003
24004 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
24005 let candidate: String = snapshot
24006 .chars_for_range(range.clone())
24007 .skip(num_of_whitespaces)
24008 .take(max_prefix_len)
24009 .collect();
24010
24011 if let Some((prefix, continuation)) = all_entries
24012 .iter()
24013 .filter(|(prefix, _)| candidate.starts_with(*prefix))
24014 .max_by_key(|(prefix, _)| prefix.len())
24015 {
24016 let end_of_prefix = num_of_whitespaces + prefix.len();
24017 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
24018 let has_content_after_marker = snapshot
24019 .chars_for_range(range)
24020 .skip(end_of_prefix)
24021 .any(|c| !c.is_whitespace());
24022
24023 if has_content_after_marker && cursor_is_after_prefix {
24024 return Some((*continuation).into());
24025 }
24026
24027 if start_point.column as usize == end_of_prefix {
24028 if num_of_whitespaces == 0 {
24029 *newline_config = NewlineConfig::ClearCurrentLine;
24030 } else {
24031 *newline_config = NewlineConfig::UnindentCurrentLine {
24032 continuation: (*continuation).into(),
24033 };
24034 }
24035 }
24036
24037 return None;
24038 }
24039 }
24040
24041 let candidate: String = snapshot
24042 .chars_for_range(range.clone())
24043 .skip(num_of_whitespaces)
24044 .take(ORDERED_LIST_MAX_MARKER_LEN)
24045 .collect();
24046
24047 for ordered_config in language.ordered_list() {
24048 let regex = match Regex::new(&ordered_config.pattern) {
24049 Ok(r) => r,
24050 Err(_) => continue,
24051 };
24052
24053 if let Some(captures) = regex.captures(&candidate) {
24054 let full_match = captures.get(0)?;
24055 let marker_len = full_match.len();
24056 let end_of_prefix = num_of_whitespaces + marker_len;
24057 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
24058
24059 let has_content_after_marker = snapshot
24060 .chars_for_range(range)
24061 .skip(end_of_prefix)
24062 .any(|c| !c.is_whitespace());
24063
24064 if has_content_after_marker && cursor_is_after_prefix {
24065 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
24066 let continuation = ordered_config
24067 .format
24068 .replace("{1}", &(number + 1).to_string());
24069 return Some(continuation.into());
24070 }
24071
24072 if start_point.column as usize == end_of_prefix {
24073 let continuation = ordered_config.format.replace("{1}", "1");
24074 if num_of_whitespaces == 0 {
24075 *newline_config = NewlineConfig::ClearCurrentLine;
24076 } else {
24077 *newline_config = NewlineConfig::UnindentCurrentLine {
24078 continuation: continuation.into(),
24079 };
24080 }
24081 }
24082
24083 return None;
24084 }
24085 }
24086
24087 None
24088}
24089
24090fn is_list_prefix_row(
24091 row: MultiBufferRow,
24092 buffer: &MultiBufferSnapshot,
24093 language: &LanguageScope,
24094) -> bool {
24095 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
24096 return false;
24097 };
24098
24099 let num_of_whitespaces = snapshot
24100 .chars_for_range(range.clone())
24101 .take_while(|c| c.is_whitespace())
24102 .count();
24103
24104 let task_list_prefixes: Vec<_> = language
24105 .task_list()
24106 .into_iter()
24107 .flat_map(|config| {
24108 config
24109 .prefixes
24110 .iter()
24111 .map(|p| p.as_ref())
24112 .collect::<Vec<_>>()
24113 })
24114 .collect();
24115 let unordered_list_markers: Vec<_> = language
24116 .unordered_list()
24117 .iter()
24118 .map(|marker| marker.as_ref())
24119 .collect();
24120 let all_prefixes: Vec<_> = task_list_prefixes
24121 .into_iter()
24122 .chain(unordered_list_markers)
24123 .collect();
24124 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
24125 let candidate: String = snapshot
24126 .chars_for_range(range.clone())
24127 .skip(num_of_whitespaces)
24128 .take(max_prefix_len)
24129 .collect();
24130 if all_prefixes
24131 .iter()
24132 .any(|prefix| candidate.starts_with(*prefix))
24133 {
24134 return true;
24135 }
24136 }
24137
24138 let ordered_list_candidate: String = snapshot
24139 .chars_for_range(range)
24140 .skip(num_of_whitespaces)
24141 .take(ORDERED_LIST_MAX_MARKER_LEN)
24142 .collect();
24143 for ordered_config in language.ordered_list() {
24144 let regex = match Regex::new(&ordered_config.pattern) {
24145 Ok(r) => r,
24146 Err(_) => continue,
24147 };
24148 if let Some(captures) = regex.captures(&ordered_list_candidate) {
24149 return captures.get(0).is_some();
24150 }
24151 }
24152
24153 false
24154}
24155
24156#[derive(Debug)]
24157enum NewlineConfig {
24158 /// Insert newline with optional additional indent and optional extra blank line
24159 Newline {
24160 additional_indent: IndentSize,
24161 extra_line_additional_indent: Option<IndentSize>,
24162 prevent_auto_indent: bool,
24163 },
24164 /// Clear the current line
24165 ClearCurrentLine,
24166 /// Unindent the current line and add continuation
24167 UnindentCurrentLine { continuation: Arc<str> },
24168}
24169
24170impl NewlineConfig {
24171 fn has_extra_line(&self) -> bool {
24172 matches!(
24173 self,
24174 Self::Newline {
24175 extra_line_additional_indent: Some(_),
24176 ..
24177 }
24178 )
24179 }
24180
24181 fn insert_extra_newline_brackets(
24182 buffer: &MultiBufferSnapshot,
24183 range: Range<MultiBufferOffset>,
24184 language: &language::LanguageScope,
24185 ) -> bool {
24186 let leading_whitespace_len = buffer
24187 .reversed_chars_at(range.start)
24188 .take_while(|c| c.is_whitespace() && *c != '\n')
24189 .map(|c| c.len_utf8())
24190 .sum::<usize>();
24191 let trailing_whitespace_len = buffer
24192 .chars_at(range.end)
24193 .take_while(|c| c.is_whitespace() && *c != '\n')
24194 .map(|c| c.len_utf8())
24195 .sum::<usize>();
24196 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
24197
24198 language.brackets().any(|(pair, enabled)| {
24199 let pair_start = pair.start.trim_end();
24200 let pair_end = pair.end.trim_start();
24201
24202 enabled
24203 && pair.newline
24204 && buffer.contains_str_at(range.end, pair_end)
24205 && buffer.contains_str_at(
24206 range.start.saturating_sub_usize(pair_start.len()),
24207 pair_start,
24208 )
24209 })
24210 }
24211
24212 fn insert_extra_newline_tree_sitter(
24213 buffer: &MultiBufferSnapshot,
24214 range: Range<MultiBufferOffset>,
24215 ) -> bool {
24216 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
24217 [(buffer, range, _)] => (*buffer, range.clone()),
24218 _ => return false,
24219 };
24220 let pair = {
24221 let mut result: Option<BracketMatch<usize>> = None;
24222
24223 for pair in buffer
24224 .all_bracket_ranges(range.start.0..range.end.0)
24225 .filter(move |pair| {
24226 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
24227 })
24228 {
24229 let len = pair.close_range.end - pair.open_range.start;
24230
24231 if let Some(existing) = &result {
24232 let existing_len = existing.close_range.end - existing.open_range.start;
24233 if len > existing_len {
24234 continue;
24235 }
24236 }
24237
24238 result = Some(pair);
24239 }
24240
24241 result
24242 };
24243 let Some(pair) = pair else {
24244 return false;
24245 };
24246 pair.newline_only
24247 && buffer
24248 .chars_for_range(pair.open_range.end..range.start.0)
24249 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
24250 .all(|c| c.is_whitespace() && c != '\n')
24251 }
24252}
24253
24254fn update_uncommitted_diff_for_buffer(
24255 editor: Entity<Editor>,
24256 project: &Entity<Project>,
24257 buffers: impl IntoIterator<Item = Entity<Buffer>>,
24258 buffer: Entity<MultiBuffer>,
24259 cx: &mut App,
24260) -> Task<()> {
24261 let mut tasks = Vec::new();
24262 project.update(cx, |project, cx| {
24263 for buffer in buffers {
24264 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
24265 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
24266 }
24267 }
24268 });
24269 cx.spawn(async move |cx| {
24270 let diffs = future::join_all(tasks).await;
24271 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
24272 return;
24273 }
24274
24275 buffer.update(cx, |buffer, cx| {
24276 for diff in diffs.into_iter().flatten() {
24277 buffer.add_diff(diff, cx);
24278 }
24279 });
24280 })
24281}
24282
24283fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
24284 let tab_size = tab_size.get() as usize;
24285 let mut width = offset;
24286
24287 for ch in text.chars() {
24288 width += if ch == '\t' {
24289 tab_size - (width % tab_size)
24290 } else {
24291 1
24292 };
24293 }
24294
24295 width - offset
24296}
24297
24298#[cfg(test)]
24299mod tests {
24300 use super::*;
24301
24302 #[test]
24303 fn test_string_size_with_expanded_tabs() {
24304 let nz = |val| NonZeroU32::new(val).unwrap();
24305 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
24306 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
24307 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
24308 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
24309 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
24310 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
24311 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
24312 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
24313 }
24314}
24315
24316/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
24317struct WordBreakingTokenizer<'a> {
24318 input: &'a str,
24319}
24320
24321impl<'a> WordBreakingTokenizer<'a> {
24322 fn new(input: &'a str) -> Self {
24323 Self { input }
24324 }
24325}
24326
24327fn is_char_ideographic(ch: char) -> bool {
24328 use unicode_script::Script::*;
24329 use unicode_script::UnicodeScript;
24330 matches!(ch.script(), Han | Tangut | Yi)
24331}
24332
24333fn is_grapheme_ideographic(text: &str) -> bool {
24334 text.chars().any(is_char_ideographic)
24335}
24336
24337fn is_grapheme_whitespace(text: &str) -> bool {
24338 text.chars().any(|x| x.is_whitespace())
24339}
24340
24341fn should_stay_with_preceding_ideograph(text: &str) -> bool {
24342 text.chars()
24343 .next()
24344 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
24345}
24346
24347#[derive(PartialEq, Eq, Debug, Clone, Copy)]
24348enum WordBreakToken<'a> {
24349 Word { token: &'a str, grapheme_len: usize },
24350 InlineWhitespace { token: &'a str, grapheme_len: usize },
24351 Newline,
24352}
24353
24354impl<'a> Iterator for WordBreakingTokenizer<'a> {
24355 /// Yields a span, the count of graphemes in the token, and whether it was
24356 /// whitespace. Note that it also breaks at word boundaries.
24357 type Item = WordBreakToken<'a>;
24358
24359 fn next(&mut self) -> Option<Self::Item> {
24360 use unicode_segmentation::UnicodeSegmentation;
24361 if self.input.is_empty() {
24362 return None;
24363 }
24364
24365 let mut iter = self.input.graphemes(true).peekable();
24366 let mut offset = 0;
24367 let mut grapheme_len = 0;
24368 if let Some(first_grapheme) = iter.next() {
24369 let is_newline = first_grapheme == "\n";
24370 let is_whitespace = is_grapheme_whitespace(first_grapheme);
24371 offset += first_grapheme.len();
24372 grapheme_len += 1;
24373 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
24374 if let Some(grapheme) = iter.peek().copied()
24375 && should_stay_with_preceding_ideograph(grapheme)
24376 {
24377 offset += grapheme.len();
24378 grapheme_len += 1;
24379 }
24380 } else {
24381 let mut words = self.input[offset..].split_word_bound_indices().peekable();
24382 let mut next_word_bound = words.peek().copied();
24383 if next_word_bound.is_some_and(|(i, _)| i == 0) {
24384 next_word_bound = words.next();
24385 }
24386 while let Some(grapheme) = iter.peek().copied() {
24387 if next_word_bound.is_some_and(|(i, _)| i == offset) {
24388 break;
24389 };
24390 if is_grapheme_whitespace(grapheme) != is_whitespace
24391 || (grapheme == "\n") != is_newline
24392 {
24393 break;
24394 };
24395 offset += grapheme.len();
24396 grapheme_len += 1;
24397 iter.next();
24398 }
24399 }
24400 let token = &self.input[..offset];
24401 self.input = &self.input[offset..];
24402 if token == "\n" {
24403 Some(WordBreakToken::Newline)
24404 } else if is_whitespace {
24405 Some(WordBreakToken::InlineWhitespace {
24406 token,
24407 grapheme_len,
24408 })
24409 } else {
24410 Some(WordBreakToken::Word {
24411 token,
24412 grapheme_len,
24413 })
24414 }
24415 } else {
24416 None
24417 }
24418 }
24419}
24420
24421#[test]
24422fn test_word_breaking_tokenizer() {
24423 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
24424 ("", &[]),
24425 (" ", &[whitespace(" ", 2)]),
24426 ("Ʒ", &[word("Ʒ", 1)]),
24427 ("Ǽ", &[word("Ǽ", 1)]),
24428 ("⋑", &[word("⋑", 1)]),
24429 ("⋑⋑", &[word("⋑⋑", 2)]),
24430 (
24431 "原理,进而",
24432 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
24433 ),
24434 (
24435 "hello world",
24436 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
24437 ),
24438 (
24439 "hello, world",
24440 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
24441 ),
24442 (
24443 " hello world",
24444 &[
24445 whitespace(" ", 2),
24446 word("hello", 5),
24447 whitespace(" ", 1),
24448 word("world", 5),
24449 ],
24450 ),
24451 (
24452 "这是什么 \n 钢笔",
24453 &[
24454 word("这", 1),
24455 word("是", 1),
24456 word("什", 1),
24457 word("么", 1),
24458 whitespace(" ", 1),
24459 newline(),
24460 whitespace(" ", 1),
24461 word("钢", 1),
24462 word("笔", 1),
24463 ],
24464 ),
24465 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
24466 ];
24467
24468 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24469 WordBreakToken::Word {
24470 token,
24471 grapheme_len,
24472 }
24473 }
24474
24475 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24476 WordBreakToken::InlineWhitespace {
24477 token,
24478 grapheme_len,
24479 }
24480 }
24481
24482 fn newline() -> WordBreakToken<'static> {
24483 WordBreakToken::Newline
24484 }
24485
24486 for (input, result) in tests {
24487 assert_eq!(
24488 WordBreakingTokenizer::new(input)
24489 .collect::<Vec<_>>()
24490 .as_slice(),
24491 *result,
24492 );
24493 }
24494}
24495
24496fn wrap_with_prefix(
24497 first_line_prefix: String,
24498 subsequent_lines_prefix: String,
24499 unwrapped_text: String,
24500 wrap_column: usize,
24501 tab_size: NonZeroU32,
24502 preserve_existing_whitespace: bool,
24503) -> String {
24504 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
24505 let subsequent_lines_prefix_len =
24506 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
24507 let mut wrapped_text = String::new();
24508 let mut current_line = first_line_prefix;
24509 let mut is_first_line = true;
24510
24511 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
24512 let mut current_line_len = first_line_prefix_len;
24513 let mut in_whitespace = false;
24514 for token in tokenizer {
24515 let have_preceding_whitespace = in_whitespace;
24516 match token {
24517 WordBreakToken::Word {
24518 token,
24519 grapheme_len,
24520 } => {
24521 in_whitespace = false;
24522 let current_prefix_len = if is_first_line {
24523 first_line_prefix_len
24524 } else {
24525 subsequent_lines_prefix_len
24526 };
24527 if current_line_len + grapheme_len > wrap_column
24528 && current_line_len != current_prefix_len
24529 {
24530 wrapped_text.push_str(current_line.trim_end());
24531 wrapped_text.push('\n');
24532 is_first_line = false;
24533 current_line = subsequent_lines_prefix.clone();
24534 current_line_len = subsequent_lines_prefix_len;
24535 }
24536 current_line.push_str(token);
24537 current_line_len += grapheme_len;
24538 }
24539 WordBreakToken::InlineWhitespace {
24540 mut token,
24541 mut grapheme_len,
24542 } => {
24543 in_whitespace = true;
24544 if have_preceding_whitespace && !preserve_existing_whitespace {
24545 continue;
24546 }
24547 if !preserve_existing_whitespace {
24548 // Keep a single whitespace grapheme as-is
24549 if let Some(first) =
24550 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
24551 {
24552 token = first;
24553 } else {
24554 token = " ";
24555 }
24556 grapheme_len = 1;
24557 }
24558 let current_prefix_len = if is_first_line {
24559 first_line_prefix_len
24560 } else {
24561 subsequent_lines_prefix_len
24562 };
24563 if current_line_len + grapheme_len > wrap_column {
24564 wrapped_text.push_str(current_line.trim_end());
24565 wrapped_text.push('\n');
24566 is_first_line = false;
24567 current_line = subsequent_lines_prefix.clone();
24568 current_line_len = subsequent_lines_prefix_len;
24569 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
24570 current_line.push_str(token);
24571 current_line_len += grapheme_len;
24572 }
24573 }
24574 WordBreakToken::Newline => {
24575 in_whitespace = true;
24576 let current_prefix_len = if is_first_line {
24577 first_line_prefix_len
24578 } else {
24579 subsequent_lines_prefix_len
24580 };
24581 if preserve_existing_whitespace {
24582 wrapped_text.push_str(current_line.trim_end());
24583 wrapped_text.push('\n');
24584 is_first_line = false;
24585 current_line = subsequent_lines_prefix.clone();
24586 current_line_len = subsequent_lines_prefix_len;
24587 } else if have_preceding_whitespace {
24588 continue;
24589 } else if current_line_len + 1 > wrap_column
24590 && current_line_len != current_prefix_len
24591 {
24592 wrapped_text.push_str(current_line.trim_end());
24593 wrapped_text.push('\n');
24594 is_first_line = false;
24595 current_line = subsequent_lines_prefix.clone();
24596 current_line_len = subsequent_lines_prefix_len;
24597 } else if current_line_len != current_prefix_len {
24598 current_line.push(' ');
24599 current_line_len += 1;
24600 }
24601 }
24602 }
24603 }
24604
24605 if !current_line.is_empty() {
24606 wrapped_text.push_str(¤t_line);
24607 }
24608 wrapped_text
24609}
24610
24611#[test]
24612fn test_wrap_with_prefix() {
24613 assert_eq!(
24614 wrap_with_prefix(
24615 "# ".to_string(),
24616 "# ".to_string(),
24617 "abcdefg".to_string(),
24618 4,
24619 NonZeroU32::new(4).unwrap(),
24620 false,
24621 ),
24622 "# abcdefg"
24623 );
24624 assert_eq!(
24625 wrap_with_prefix(
24626 "".to_string(),
24627 "".to_string(),
24628 "\thello world".to_string(),
24629 8,
24630 NonZeroU32::new(4).unwrap(),
24631 false,
24632 ),
24633 "hello\nworld"
24634 );
24635 assert_eq!(
24636 wrap_with_prefix(
24637 "// ".to_string(),
24638 "// ".to_string(),
24639 "xx \nyy zz aa bb cc".to_string(),
24640 12,
24641 NonZeroU32::new(4).unwrap(),
24642 false,
24643 ),
24644 "// xx yy zz\n// aa bb cc"
24645 );
24646 assert_eq!(
24647 wrap_with_prefix(
24648 String::new(),
24649 String::new(),
24650 "这是什么 \n 钢笔".to_string(),
24651 3,
24652 NonZeroU32::new(4).unwrap(),
24653 false,
24654 ),
24655 "这是什\n么 钢\n笔"
24656 );
24657 assert_eq!(
24658 wrap_with_prefix(
24659 String::new(),
24660 String::new(),
24661 format!("foo{}bar", '\u{2009}'), // thin space
24662 80,
24663 NonZeroU32::new(4).unwrap(),
24664 false,
24665 ),
24666 format!("foo{}bar", '\u{2009}')
24667 );
24668}
24669
24670pub trait CollaborationHub {
24671 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24672 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24673 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24674}
24675
24676impl CollaborationHub for Entity<Project> {
24677 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24678 self.read(cx).collaborators()
24679 }
24680
24681 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24682 self.read(cx).user_store().read(cx).participant_indices()
24683 }
24684
24685 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24686 let this = self.read(cx);
24687 let user_ids = this.collaborators().values().map(|c| c.user_id);
24688 this.user_store().read(cx).participant_names(user_ids, cx)
24689 }
24690}
24691
24692pub trait SemanticsProvider {
24693 fn hover(
24694 &self,
24695 buffer: &Entity<Buffer>,
24696 position: text::Anchor,
24697 cx: &mut App,
24698 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24699
24700 fn inline_values(
24701 &self,
24702 buffer_handle: Entity<Buffer>,
24703 range: Range<text::Anchor>,
24704 cx: &mut App,
24705 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24706
24707 fn applicable_inlay_chunks(
24708 &self,
24709 buffer: &Entity<Buffer>,
24710 ranges: &[Range<text::Anchor>],
24711 cx: &mut App,
24712 ) -> Vec<Range<BufferRow>>;
24713
24714 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24715
24716 fn inlay_hints(
24717 &self,
24718 invalidate: InvalidationStrategy,
24719 buffer: Entity<Buffer>,
24720 ranges: Vec<Range<text::Anchor>>,
24721 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24722 cx: &mut App,
24723 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24724
24725 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24726
24727 fn document_highlights(
24728 &self,
24729 buffer: &Entity<Buffer>,
24730 position: text::Anchor,
24731 cx: &mut App,
24732 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24733
24734 fn definitions(
24735 &self,
24736 buffer: &Entity<Buffer>,
24737 position: text::Anchor,
24738 kind: GotoDefinitionKind,
24739 cx: &mut App,
24740 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24741
24742 fn range_for_rename(
24743 &self,
24744 buffer: &Entity<Buffer>,
24745 position: text::Anchor,
24746 cx: &mut App,
24747 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24748
24749 fn perform_rename(
24750 &self,
24751 buffer: &Entity<Buffer>,
24752 position: text::Anchor,
24753 new_name: String,
24754 cx: &mut App,
24755 ) -> Option<Task<Result<ProjectTransaction>>>;
24756}
24757
24758pub trait CompletionProvider {
24759 fn completions(
24760 &self,
24761 excerpt_id: ExcerptId,
24762 buffer: &Entity<Buffer>,
24763 buffer_position: text::Anchor,
24764 trigger: CompletionContext,
24765 window: &mut Window,
24766 cx: &mut Context<Editor>,
24767 ) -> Task<Result<Vec<CompletionResponse>>>;
24768
24769 fn resolve_completions(
24770 &self,
24771 _buffer: Entity<Buffer>,
24772 _completion_indices: Vec<usize>,
24773 _completions: Rc<RefCell<Box<[Completion]>>>,
24774 _cx: &mut Context<Editor>,
24775 ) -> Task<Result<bool>> {
24776 Task::ready(Ok(false))
24777 }
24778
24779 fn apply_additional_edits_for_completion(
24780 &self,
24781 _buffer: Entity<Buffer>,
24782 _completions: Rc<RefCell<Box<[Completion]>>>,
24783 _completion_index: usize,
24784 _push_to_history: bool,
24785 _cx: &mut Context<Editor>,
24786 ) -> Task<Result<Option<language::Transaction>>> {
24787 Task::ready(Ok(None))
24788 }
24789
24790 fn is_completion_trigger(
24791 &self,
24792 buffer: &Entity<Buffer>,
24793 position: language::Anchor,
24794 text: &str,
24795 trigger_in_words: bool,
24796 cx: &mut Context<Editor>,
24797 ) -> bool;
24798
24799 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24800
24801 fn sort_completions(&self) -> bool {
24802 true
24803 }
24804
24805 fn filter_completions(&self) -> bool {
24806 true
24807 }
24808
24809 fn show_snippets(&self) -> bool {
24810 false
24811 }
24812}
24813
24814pub trait CodeActionProvider {
24815 fn id(&self) -> Arc<str>;
24816
24817 fn code_actions(
24818 &self,
24819 buffer: &Entity<Buffer>,
24820 range: Range<text::Anchor>,
24821 window: &mut Window,
24822 cx: &mut App,
24823 ) -> Task<Result<Vec<CodeAction>>>;
24824
24825 fn apply_code_action(
24826 &self,
24827 buffer_handle: Entity<Buffer>,
24828 action: CodeAction,
24829 excerpt_id: ExcerptId,
24830 push_to_history: bool,
24831 window: &mut Window,
24832 cx: &mut App,
24833 ) -> Task<Result<ProjectTransaction>>;
24834}
24835
24836impl CodeActionProvider for Entity<Project> {
24837 fn id(&self) -> Arc<str> {
24838 "project".into()
24839 }
24840
24841 fn code_actions(
24842 &self,
24843 buffer: &Entity<Buffer>,
24844 range: Range<text::Anchor>,
24845 _window: &mut Window,
24846 cx: &mut App,
24847 ) -> Task<Result<Vec<CodeAction>>> {
24848 self.update(cx, |project, cx| {
24849 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24850 let code_actions = project.code_actions(buffer, range, None, cx);
24851 cx.background_spawn(async move {
24852 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24853 Ok(code_lens_actions
24854 .context("code lens fetch")?
24855 .into_iter()
24856 .flatten()
24857 .chain(
24858 code_actions
24859 .context("code action fetch")?
24860 .into_iter()
24861 .flatten(),
24862 )
24863 .collect())
24864 })
24865 })
24866 }
24867
24868 fn apply_code_action(
24869 &self,
24870 buffer_handle: Entity<Buffer>,
24871 action: CodeAction,
24872 _excerpt_id: ExcerptId,
24873 push_to_history: bool,
24874 _window: &mut Window,
24875 cx: &mut App,
24876 ) -> Task<Result<ProjectTransaction>> {
24877 self.update(cx, |project, cx| {
24878 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24879 })
24880 }
24881}
24882
24883fn snippet_completions(
24884 project: &Project,
24885 buffer: &Entity<Buffer>,
24886 buffer_anchor: text::Anchor,
24887 classifier: CharClassifier,
24888 cx: &mut App,
24889) -> Task<Result<CompletionResponse>> {
24890 let languages = buffer.read(cx).languages_at(buffer_anchor);
24891 let snippet_store = project.snippets().read(cx);
24892
24893 let scopes: Vec<_> = languages
24894 .iter()
24895 .filter_map(|language| {
24896 let language_name = language.lsp_id();
24897 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24898
24899 if snippets.is_empty() {
24900 None
24901 } else {
24902 Some((language.default_scope(), snippets))
24903 }
24904 })
24905 .collect();
24906
24907 if scopes.is_empty() {
24908 return Task::ready(Ok(CompletionResponse {
24909 completions: vec![],
24910 display_options: CompletionDisplayOptions::default(),
24911 is_incomplete: false,
24912 }));
24913 }
24914
24915 let snapshot = buffer.read(cx).text_snapshot();
24916 let executor = cx.background_executor().clone();
24917
24918 cx.background_spawn(async move {
24919 let is_word_char = |c| classifier.is_word(c);
24920
24921 let mut is_incomplete = false;
24922 let mut completions: Vec<Completion> = Vec::new();
24923
24924 const MAX_PREFIX_LEN: usize = 128;
24925 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24926 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24927 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24928
24929 let max_buffer_window: String = snapshot
24930 .text_for_range(window_start..buffer_offset)
24931 .collect();
24932
24933 if max_buffer_window.is_empty() {
24934 return Ok(CompletionResponse {
24935 completions: vec![],
24936 display_options: CompletionDisplayOptions::default(),
24937 is_incomplete: true,
24938 });
24939 }
24940
24941 for (_scope, snippets) in scopes.into_iter() {
24942 // Sort snippets by word count to match longer snippet prefixes first.
24943 let mut sorted_snippet_candidates = snippets
24944 .iter()
24945 .enumerate()
24946 .flat_map(|(snippet_ix, snippet)| {
24947 snippet
24948 .prefix
24949 .iter()
24950 .enumerate()
24951 .map(move |(prefix_ix, prefix)| {
24952 let word_count =
24953 snippet_candidate_suffixes(prefix, is_word_char).count();
24954 ((snippet_ix, prefix_ix), prefix, word_count)
24955 })
24956 })
24957 .collect_vec();
24958 sorted_snippet_candidates
24959 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24960
24961 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24962
24963 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24964 .take(
24965 sorted_snippet_candidates
24966 .first()
24967 .map(|(_, _, word_count)| *word_count)
24968 .unwrap_or_default(),
24969 )
24970 .collect_vec();
24971
24972 const MAX_RESULTS: usize = 100;
24973 // Each match also remembers how many characters from the buffer it consumed
24974 let mut matches: Vec<(StringMatch, usize)> = vec![];
24975
24976 let mut snippet_list_cutoff_index = 0;
24977 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24978 let word_count = buffer_index + 1;
24979 // Increase `snippet_list_cutoff_index` until we have all of the
24980 // snippets with sufficiently many words.
24981 while sorted_snippet_candidates
24982 .get(snippet_list_cutoff_index)
24983 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24984 *snippet_word_count >= word_count
24985 })
24986 {
24987 snippet_list_cutoff_index += 1;
24988 }
24989
24990 // Take only the candidates with at least `word_count` many words
24991 let snippet_candidates_at_word_len =
24992 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24993
24994 let candidates = snippet_candidates_at_word_len
24995 .iter()
24996 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24997 .enumerate() // index in `sorted_snippet_candidates`
24998 // First char must match
24999 .filter(|(_ix, prefix)| {
25000 itertools::equal(
25001 prefix
25002 .chars()
25003 .next()
25004 .into_iter()
25005 .flat_map(|c| c.to_lowercase()),
25006 buffer_window
25007 .chars()
25008 .next()
25009 .into_iter()
25010 .flat_map(|c| c.to_lowercase()),
25011 )
25012 })
25013 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
25014 .collect::<Vec<StringMatchCandidate>>();
25015
25016 matches.extend(
25017 fuzzy::match_strings(
25018 &candidates,
25019 &buffer_window,
25020 buffer_window.chars().any(|c| c.is_uppercase()),
25021 true,
25022 MAX_RESULTS - matches.len(), // always prioritize longer snippets
25023 &Default::default(),
25024 executor.clone(),
25025 )
25026 .await
25027 .into_iter()
25028 .map(|string_match| (string_match, buffer_window.len())),
25029 );
25030
25031 if matches.len() >= MAX_RESULTS {
25032 break;
25033 }
25034 }
25035
25036 let to_lsp = |point: &text::Anchor| {
25037 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
25038 point_to_lsp(end)
25039 };
25040 let lsp_end = to_lsp(&buffer_anchor);
25041
25042 if matches.len() >= MAX_RESULTS {
25043 is_incomplete = true;
25044 }
25045
25046 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
25047 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
25048 sorted_snippet_candidates[string_match.candidate_id];
25049 let snippet = &snippets[snippet_index];
25050 let start = buffer_offset - buffer_window_len;
25051 let start = snapshot.anchor_before(start);
25052 let range = start..buffer_anchor;
25053 let lsp_start = to_lsp(&start);
25054 let lsp_range = lsp::Range {
25055 start: lsp_start,
25056 end: lsp_end,
25057 };
25058 Completion {
25059 replace_range: range,
25060 new_text: snippet.body.clone(),
25061 source: CompletionSource::Lsp {
25062 insert_range: None,
25063 server_id: LanguageServerId(usize::MAX),
25064 resolved: true,
25065 lsp_completion: Box::new(lsp::CompletionItem {
25066 label: snippet.prefix.first().unwrap().clone(),
25067 kind: Some(CompletionItemKind::SNIPPET),
25068 label_details: snippet.description.as_ref().map(|description| {
25069 lsp::CompletionItemLabelDetails {
25070 detail: Some(description.clone()),
25071 description: None,
25072 }
25073 }),
25074 insert_text_format: Some(InsertTextFormat::SNIPPET),
25075 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25076 lsp::InsertReplaceEdit {
25077 new_text: snippet.body.clone(),
25078 insert: lsp_range,
25079 replace: lsp_range,
25080 },
25081 )),
25082 filter_text: Some(snippet.body.clone()),
25083 sort_text: Some(char::MAX.to_string()),
25084 ..lsp::CompletionItem::default()
25085 }),
25086 lsp_defaults: None,
25087 },
25088 label: CodeLabel {
25089 text: matching_prefix.clone(),
25090 runs: Vec::new(),
25091 filter_range: 0..matching_prefix.len(),
25092 },
25093 icon_path: None,
25094 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
25095 single_line: snippet.name.clone().into(),
25096 plain_text: snippet
25097 .description
25098 .clone()
25099 .map(|description| description.into()),
25100 }),
25101 insert_text_mode: None,
25102 confirm: None,
25103 match_start: Some(start),
25104 snippet_deduplication_key: Some((snippet_index, prefix_index)),
25105 }
25106 }));
25107 }
25108
25109 Ok(CompletionResponse {
25110 completions,
25111 display_options: CompletionDisplayOptions::default(),
25112 is_incomplete,
25113 })
25114 })
25115}
25116
25117impl CompletionProvider for Entity<Project> {
25118 fn completions(
25119 &self,
25120 _excerpt_id: ExcerptId,
25121 buffer: &Entity<Buffer>,
25122 buffer_position: text::Anchor,
25123 options: CompletionContext,
25124 _window: &mut Window,
25125 cx: &mut Context<Editor>,
25126 ) -> Task<Result<Vec<CompletionResponse>>> {
25127 self.update(cx, |project, cx| {
25128 let task = project.completions(buffer, buffer_position, options, cx);
25129 cx.background_spawn(task)
25130 })
25131 }
25132
25133 fn resolve_completions(
25134 &self,
25135 buffer: Entity<Buffer>,
25136 completion_indices: Vec<usize>,
25137 completions: Rc<RefCell<Box<[Completion]>>>,
25138 cx: &mut Context<Editor>,
25139 ) -> Task<Result<bool>> {
25140 self.update(cx, |project, cx| {
25141 project.lsp_store().update(cx, |lsp_store, cx| {
25142 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
25143 })
25144 })
25145 }
25146
25147 fn apply_additional_edits_for_completion(
25148 &self,
25149 buffer: Entity<Buffer>,
25150 completions: Rc<RefCell<Box<[Completion]>>>,
25151 completion_index: usize,
25152 push_to_history: bool,
25153 cx: &mut Context<Editor>,
25154 ) -> Task<Result<Option<language::Transaction>>> {
25155 self.update(cx, |project, cx| {
25156 project.lsp_store().update(cx, |lsp_store, cx| {
25157 lsp_store.apply_additional_edits_for_completion(
25158 buffer,
25159 completions,
25160 completion_index,
25161 push_to_history,
25162 cx,
25163 )
25164 })
25165 })
25166 }
25167
25168 fn is_completion_trigger(
25169 &self,
25170 buffer: &Entity<Buffer>,
25171 position: language::Anchor,
25172 text: &str,
25173 trigger_in_words: bool,
25174 cx: &mut Context<Editor>,
25175 ) -> bool {
25176 let mut chars = text.chars();
25177 let char = if let Some(char) = chars.next() {
25178 char
25179 } else {
25180 return false;
25181 };
25182 if chars.next().is_some() {
25183 return false;
25184 }
25185
25186 let buffer = buffer.read(cx);
25187 let snapshot = buffer.snapshot();
25188 let classifier = snapshot
25189 .char_classifier_at(position)
25190 .scope_context(Some(CharScopeContext::Completion));
25191 if trigger_in_words && classifier.is_word(char) {
25192 return true;
25193 }
25194
25195 buffer.completion_triggers().contains(text)
25196 }
25197
25198 fn show_snippets(&self) -> bool {
25199 true
25200 }
25201}
25202
25203impl SemanticsProvider for Entity<Project> {
25204 fn hover(
25205 &self,
25206 buffer: &Entity<Buffer>,
25207 position: text::Anchor,
25208 cx: &mut App,
25209 ) -> Option<Task<Option<Vec<project::Hover>>>> {
25210 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
25211 }
25212
25213 fn document_highlights(
25214 &self,
25215 buffer: &Entity<Buffer>,
25216 position: text::Anchor,
25217 cx: &mut App,
25218 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
25219 Some(self.update(cx, |project, cx| {
25220 project.document_highlights(buffer, position, cx)
25221 }))
25222 }
25223
25224 fn definitions(
25225 &self,
25226 buffer: &Entity<Buffer>,
25227 position: text::Anchor,
25228 kind: GotoDefinitionKind,
25229 cx: &mut App,
25230 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
25231 Some(self.update(cx, |project, cx| match kind {
25232 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
25233 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
25234 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
25235 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
25236 }))
25237 }
25238
25239 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
25240 self.update(cx, |project, cx| {
25241 if project
25242 .active_debug_session(cx)
25243 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
25244 {
25245 return true;
25246 }
25247
25248 buffer.update(cx, |buffer, cx| {
25249 project.any_language_server_supports_inlay_hints(buffer, cx)
25250 })
25251 })
25252 }
25253
25254 fn inline_values(
25255 &self,
25256 buffer_handle: Entity<Buffer>,
25257 range: Range<text::Anchor>,
25258 cx: &mut App,
25259 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
25260 self.update(cx, |project, cx| {
25261 let (session, active_stack_frame) = project.active_debug_session(cx)?;
25262
25263 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
25264 })
25265 }
25266
25267 fn applicable_inlay_chunks(
25268 &self,
25269 buffer: &Entity<Buffer>,
25270 ranges: &[Range<text::Anchor>],
25271 cx: &mut App,
25272 ) -> Vec<Range<BufferRow>> {
25273 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25274 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
25275 })
25276 }
25277
25278 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
25279 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
25280 lsp_store.invalidate_inlay_hints(for_buffers)
25281 });
25282 }
25283
25284 fn inlay_hints(
25285 &self,
25286 invalidate: InvalidationStrategy,
25287 buffer: Entity<Buffer>,
25288 ranges: Vec<Range<text::Anchor>>,
25289 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
25290 cx: &mut App,
25291 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
25292 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25293 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
25294 }))
25295 }
25296
25297 fn range_for_rename(
25298 &self,
25299 buffer: &Entity<Buffer>,
25300 position: text::Anchor,
25301 cx: &mut App,
25302 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
25303 Some(self.update(cx, |project, cx| {
25304 let buffer = buffer.clone();
25305 let task = project.prepare_rename(buffer.clone(), position, cx);
25306 cx.spawn(async move |_, cx| {
25307 Ok(match task.await? {
25308 PrepareRenameResponse::Success(range) => Some(range),
25309 PrepareRenameResponse::InvalidPosition => None,
25310 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
25311 // Fallback on using TreeSitter info to determine identifier range
25312 buffer.read_with(cx, |buffer, _| {
25313 let snapshot = buffer.snapshot();
25314 let (range, kind) = snapshot.surrounding_word(position, None);
25315 if kind != Some(CharKind::Word) {
25316 return None;
25317 }
25318 Some(
25319 snapshot.anchor_before(range.start)
25320 ..snapshot.anchor_after(range.end),
25321 )
25322 })
25323 }
25324 })
25325 })
25326 }))
25327 }
25328
25329 fn perform_rename(
25330 &self,
25331 buffer: &Entity<Buffer>,
25332 position: text::Anchor,
25333 new_name: String,
25334 cx: &mut App,
25335 ) -> Option<Task<Result<ProjectTransaction>>> {
25336 Some(self.update(cx, |project, cx| {
25337 project.perform_rename(buffer.clone(), position, new_name, cx)
25338 }))
25339 }
25340}
25341
25342fn consume_contiguous_rows(
25343 contiguous_row_selections: &mut Vec<Selection<Point>>,
25344 selection: &Selection<Point>,
25345 display_map: &DisplaySnapshot,
25346 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
25347) -> (MultiBufferRow, MultiBufferRow) {
25348 contiguous_row_selections.push(selection.clone());
25349 let start_row = starting_row(selection, display_map);
25350 let mut end_row = ending_row(selection, display_map);
25351
25352 while let Some(next_selection) = selections.peek() {
25353 if next_selection.start.row <= end_row.0 {
25354 end_row = ending_row(next_selection, display_map);
25355 contiguous_row_selections.push(selections.next().unwrap().clone());
25356 } else {
25357 break;
25358 }
25359 }
25360 (start_row, end_row)
25361}
25362
25363fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25364 if selection.start.column > 0 {
25365 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
25366 } else {
25367 MultiBufferRow(selection.start.row)
25368 }
25369}
25370
25371fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25372 if next_selection.end.column > 0 || next_selection.is_empty() {
25373 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
25374 } else {
25375 MultiBufferRow(next_selection.end.row)
25376 }
25377}
25378
25379impl EditorSnapshot {
25380 pub fn remote_selections_in_range<'a>(
25381 &'a self,
25382 range: &'a Range<Anchor>,
25383 collaboration_hub: &dyn CollaborationHub,
25384 cx: &'a App,
25385 ) -> impl 'a + Iterator<Item = RemoteSelection> {
25386 let participant_names = collaboration_hub.user_names(cx);
25387 let participant_indices = collaboration_hub.user_participant_indices(cx);
25388 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
25389 let collaborators_by_replica_id = collaborators_by_peer_id
25390 .values()
25391 .map(|collaborator| (collaborator.replica_id, collaborator))
25392 .collect::<HashMap<_, _>>();
25393 self.buffer_snapshot()
25394 .selections_in_range(range, false)
25395 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
25396 if replica_id == ReplicaId::AGENT {
25397 Some(RemoteSelection {
25398 replica_id,
25399 selection,
25400 cursor_shape,
25401 line_mode,
25402 collaborator_id: CollaboratorId::Agent,
25403 user_name: Some("Agent".into()),
25404 color: cx.theme().players().agent(),
25405 })
25406 } else {
25407 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
25408 let participant_index = participant_indices.get(&collaborator.user_id).copied();
25409 let user_name = participant_names.get(&collaborator.user_id).cloned();
25410 Some(RemoteSelection {
25411 replica_id,
25412 selection,
25413 cursor_shape,
25414 line_mode,
25415 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
25416 user_name,
25417 color: if let Some(index) = participant_index {
25418 cx.theme().players().color_for_participant(index.0)
25419 } else {
25420 cx.theme().players().absent()
25421 },
25422 })
25423 }
25424 })
25425 }
25426
25427 pub fn hunks_for_ranges(
25428 &self,
25429 ranges: impl IntoIterator<Item = Range<Point>>,
25430 ) -> Vec<MultiBufferDiffHunk> {
25431 let mut hunks = Vec::new();
25432 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
25433 HashMap::default();
25434 for query_range in ranges {
25435 let query_rows =
25436 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
25437 for hunk in self.buffer_snapshot().diff_hunks_in_range(
25438 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
25439 ) {
25440 // Include deleted hunks that are adjacent to the query range, because
25441 // otherwise they would be missed.
25442 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
25443 if hunk.status().is_deleted() {
25444 intersects_range |= hunk.row_range.start == query_rows.end;
25445 intersects_range |= hunk.row_range.end == query_rows.start;
25446 }
25447 if intersects_range {
25448 if !processed_buffer_rows
25449 .entry(hunk.buffer_id)
25450 .or_default()
25451 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
25452 {
25453 continue;
25454 }
25455 hunks.push(hunk);
25456 }
25457 }
25458 }
25459
25460 hunks
25461 }
25462
25463 fn display_diff_hunks_for_rows<'a>(
25464 &'a self,
25465 display_rows: Range<DisplayRow>,
25466 folded_buffers: &'a HashSet<BufferId>,
25467 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
25468 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
25469 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
25470
25471 self.buffer_snapshot()
25472 .diff_hunks_in_range(buffer_start..buffer_end)
25473 .filter_map(|hunk| {
25474 if folded_buffers.contains(&hunk.buffer_id) {
25475 return None;
25476 }
25477
25478 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
25479 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
25480
25481 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
25482 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
25483
25484 let display_hunk = if hunk_display_start.column() != 0 {
25485 DisplayDiffHunk::Folded {
25486 display_row: hunk_display_start.row(),
25487 }
25488 } else {
25489 let mut end_row = hunk_display_end.row();
25490 if hunk_display_end.column() > 0 {
25491 end_row.0 += 1;
25492 }
25493 let is_created_file = hunk.is_created_file();
25494
25495 DisplayDiffHunk::Unfolded {
25496 status: hunk.status(),
25497 diff_base_byte_range: hunk.diff_base_byte_range.start.0
25498 ..hunk.diff_base_byte_range.end.0,
25499 word_diffs: hunk.word_diffs,
25500 display_row_range: hunk_display_start.row()..end_row,
25501 multi_buffer_range: Anchor::range_in_buffer(
25502 hunk.excerpt_id,
25503 hunk.buffer_range,
25504 ),
25505 is_created_file,
25506 }
25507 };
25508
25509 Some(display_hunk)
25510 })
25511 }
25512
25513 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
25514 self.display_snapshot
25515 .buffer_snapshot()
25516 .language_at(position)
25517 }
25518
25519 pub fn is_focused(&self) -> bool {
25520 self.is_focused
25521 }
25522
25523 pub fn placeholder_text(&self) -> Option<String> {
25524 self.placeholder_display_snapshot
25525 .as_ref()
25526 .map(|display_map| display_map.text())
25527 }
25528
25529 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
25530 self.scroll_anchor.scroll_position(&self.display_snapshot)
25531 }
25532
25533 pub fn gutter_dimensions(
25534 &self,
25535 font_id: FontId,
25536 font_size: Pixels,
25537 style: &EditorStyle,
25538 window: &mut Window,
25539 cx: &App,
25540 ) -> GutterDimensions {
25541 if self.show_gutter
25542 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
25543 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
25544 {
25545 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
25546 matches!(
25547 ProjectSettings::get_global(cx).git.git_gutter,
25548 GitGutterSetting::TrackedFiles
25549 )
25550 });
25551 let gutter_settings = EditorSettings::get_global(cx).gutter;
25552 let show_line_numbers = self
25553 .show_line_numbers
25554 .unwrap_or(gutter_settings.line_numbers);
25555 let line_gutter_width = if show_line_numbers {
25556 // Avoid flicker-like gutter resizes when the line number gains another digit by
25557 // only resizing the gutter on files with > 10**min_line_number_digits lines.
25558 let min_width_for_number_on_gutter =
25559 ch_advance * gutter_settings.min_line_number_digits as f32;
25560 self.max_line_number_width(style, window)
25561 .max(min_width_for_number_on_gutter)
25562 } else {
25563 0.0.into()
25564 };
25565
25566 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
25567 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
25568
25569 let git_blame_entries_width =
25570 self.git_blame_gutter_max_author_length
25571 .map(|max_author_length| {
25572 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
25573 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
25574
25575 /// The number of characters to dedicate to gaps and margins.
25576 const SPACING_WIDTH: usize = 4;
25577
25578 let max_char_count = max_author_length.min(renderer.max_author_length())
25579 + ::git::SHORT_SHA_LENGTH
25580 + MAX_RELATIVE_TIMESTAMP.len()
25581 + SPACING_WIDTH;
25582
25583 ch_advance * max_char_count
25584 });
25585
25586 let is_singleton = self.buffer_snapshot().is_singleton();
25587
25588 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
25589 left_padding += if !is_singleton {
25590 ch_width * 4.0
25591 } else if show_runnables || show_breakpoints {
25592 ch_width * 3.0
25593 } else if show_git_gutter && show_line_numbers {
25594 ch_width * 2.0
25595 } else if show_git_gutter || show_line_numbers {
25596 ch_width
25597 } else {
25598 px(0.)
25599 };
25600
25601 let shows_folds = is_singleton && gutter_settings.folds;
25602
25603 let right_padding = if shows_folds && show_line_numbers {
25604 ch_width * 4.0
25605 } else if shows_folds || (!is_singleton && show_line_numbers) {
25606 ch_width * 3.0
25607 } else if show_line_numbers {
25608 ch_width
25609 } else {
25610 px(0.)
25611 };
25612
25613 GutterDimensions {
25614 left_padding,
25615 right_padding,
25616 width: line_gutter_width + left_padding + right_padding,
25617 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
25618 git_blame_entries_width,
25619 }
25620 } else if self.offset_content {
25621 GutterDimensions::default_with_margin(font_id, font_size, cx)
25622 } else {
25623 GutterDimensions::default()
25624 }
25625 }
25626
25627 pub fn render_crease_toggle(
25628 &self,
25629 buffer_row: MultiBufferRow,
25630 row_contains_cursor: bool,
25631 editor: Entity<Editor>,
25632 window: &mut Window,
25633 cx: &mut App,
25634 ) -> Option<AnyElement> {
25635 let folded = self.is_line_folded(buffer_row);
25636 let mut is_foldable = false;
25637
25638 if let Some(crease) = self
25639 .crease_snapshot
25640 .query_row(buffer_row, self.buffer_snapshot())
25641 {
25642 is_foldable = true;
25643 match crease {
25644 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
25645 if let Some(render_toggle) = render_toggle {
25646 let toggle_callback =
25647 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
25648 if folded {
25649 editor.update(cx, |editor, cx| {
25650 editor.fold_at(buffer_row, window, cx)
25651 });
25652 } else {
25653 editor.update(cx, |editor, cx| {
25654 editor.unfold_at(buffer_row, window, cx)
25655 });
25656 }
25657 });
25658 return Some((render_toggle)(
25659 buffer_row,
25660 folded,
25661 toggle_callback,
25662 window,
25663 cx,
25664 ));
25665 }
25666 }
25667 }
25668 }
25669
25670 is_foldable |= self.starts_indent(buffer_row);
25671
25672 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25673 Some(
25674 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25675 .toggle_state(folded)
25676 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25677 if folded {
25678 this.unfold_at(buffer_row, window, cx);
25679 } else {
25680 this.fold_at(buffer_row, window, cx);
25681 }
25682 }))
25683 .into_any_element(),
25684 )
25685 } else {
25686 None
25687 }
25688 }
25689
25690 pub fn render_crease_trailer(
25691 &self,
25692 buffer_row: MultiBufferRow,
25693 window: &mut Window,
25694 cx: &mut App,
25695 ) -> Option<AnyElement> {
25696 let folded = self.is_line_folded(buffer_row);
25697 if let Crease::Inline { render_trailer, .. } = self
25698 .crease_snapshot
25699 .query_row(buffer_row, self.buffer_snapshot())?
25700 {
25701 let render_trailer = render_trailer.as_ref()?;
25702 Some(render_trailer(buffer_row, folded, window, cx))
25703 } else {
25704 None
25705 }
25706 }
25707
25708 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25709 let digit_count = self.widest_line_number().ilog10() + 1;
25710 column_pixels(style, digit_count as usize, window)
25711 }
25712
25713 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
25714 ///
25715 /// This is positive if `base` is before `line`.
25716 fn relative_line_delta(
25717 &self,
25718 current_selection_head: DisplayRow,
25719 first_visible_row: DisplayRow,
25720 consider_wrapped_lines: bool,
25721 ) -> i64 {
25722 let current_selection_head = current_selection_head.as_display_point().to_point(self);
25723 let first_visible_row = first_visible_row.as_display_point().to_point(self);
25724
25725 if consider_wrapped_lines {
25726 let wrap_snapshot = self.wrap_snapshot();
25727 let base_wrap_row = wrap_snapshot
25728 .make_wrap_point(current_selection_head, Bias::Left)
25729 .row();
25730 let wrap_row = wrap_snapshot
25731 .make_wrap_point(first_visible_row, Bias::Left)
25732 .row();
25733 wrap_row.0 as i64 - base_wrap_row.0 as i64
25734 } else {
25735 let folds = if current_selection_head < first_visible_row {
25736 self.folds_in_range(current_selection_head..first_visible_row)
25737 } else {
25738 self.folds_in_range(first_visible_row..current_selection_head)
25739 };
25740
25741 let folded_lines = folds
25742 .map(|fold| {
25743 let range = fold.range.0.to_point(self);
25744 range.end.row.saturating_sub(range.start.row)
25745 })
25746 .sum::<u32>() as i64;
25747
25748 first_visible_row.row as i64 - current_selection_head.row as i64 + folded_lines
25749 }
25750 }
25751
25752 /// Returns the unsigned relative line number to display for each row in `rows`.
25753 ///
25754 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
25755 pub fn calculate_relative_line_numbers(
25756 &self,
25757 rows: &Range<DisplayRow>,
25758 current_selection_head: DisplayRow,
25759 count_wrapped_lines: bool,
25760 ) -> HashMap<DisplayRow, u32> {
25761 let initial_offset =
25762 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
25763 let current_selection_point = current_selection_head.as_display_point().to_point(self);
25764
25765 self.row_infos(rows.start)
25766 .take(rows.len())
25767 .enumerate()
25768 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
25769 .filter(|(_row, row_info)| {
25770 row_info.buffer_row.is_some()
25771 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
25772 })
25773 .enumerate()
25774 .filter(|(_, (row, row_info))| {
25775 // We want to check here that
25776 // - the row is not the current selection head to ensure the current
25777 // line has absolute numbering
25778 // - similarly, should the selection head live in a soft-wrapped line
25779 // and we are not counting those, that the parent line keeps its
25780 // absolute number
25781 // - lastly, if we are in a deleted line, it is fine to number this
25782 // relative with 0, as otherwise it would have no line number at all
25783 (*row != current_selection_head
25784 && (count_wrapped_lines
25785 || row_info.buffer_row != Some(current_selection_point.row)))
25786 || row_info
25787 .diff_status
25788 .is_some_and(|status| status.is_deleted())
25789 })
25790 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
25791 .collect()
25792 }
25793}
25794
25795pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25796 let font_size = style.text.font_size.to_pixels(window.rem_size());
25797 let layout = window.text_system().shape_line(
25798 SharedString::from(" ".repeat(column)),
25799 font_size,
25800 &[TextRun {
25801 len: column,
25802 font: style.text.font(),
25803 color: Hsla::default(),
25804 ..Default::default()
25805 }],
25806 None,
25807 );
25808
25809 layout.width
25810}
25811
25812impl Deref for EditorSnapshot {
25813 type Target = DisplaySnapshot;
25814
25815 fn deref(&self) -> &Self::Target {
25816 &self.display_snapshot
25817 }
25818}
25819
25820#[derive(Clone, Debug, PartialEq, Eq)]
25821pub enum EditorEvent {
25822 InputIgnored {
25823 text: Arc<str>,
25824 },
25825 InputHandled {
25826 utf16_range_to_replace: Option<Range<isize>>,
25827 text: Arc<str>,
25828 },
25829 ExcerptsAdded {
25830 buffer: Entity<Buffer>,
25831 predecessor: ExcerptId,
25832 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25833 },
25834 ExcerptsRemoved {
25835 ids: Vec<ExcerptId>,
25836 removed_buffer_ids: Vec<BufferId>,
25837 },
25838 BufferFoldToggled {
25839 ids: Vec<ExcerptId>,
25840 folded: bool,
25841 },
25842 ExcerptsEdited {
25843 ids: Vec<ExcerptId>,
25844 },
25845 ExcerptsExpanded {
25846 ids: Vec<ExcerptId>,
25847 },
25848 ExpandExcerptsRequested {
25849 excerpt_ids: Vec<ExcerptId>,
25850 lines: u32,
25851 direction: ExpandExcerptDirection,
25852 },
25853 BufferEdited,
25854 Edited {
25855 transaction_id: clock::Lamport,
25856 },
25857 Reparsed(BufferId),
25858 Focused,
25859 FocusedIn,
25860 Blurred,
25861 DirtyChanged,
25862 Saved,
25863 TitleChanged,
25864 SelectionsChanged {
25865 local: bool,
25866 },
25867 ScrollPositionChanged {
25868 local: bool,
25869 autoscroll: bool,
25870 },
25871 TransactionUndone {
25872 transaction_id: clock::Lamport,
25873 },
25874 TransactionBegun {
25875 transaction_id: clock::Lamport,
25876 },
25877 CursorShapeChanged,
25878 BreadcrumbsChanged,
25879 PushedToNavHistory {
25880 anchor: Anchor,
25881 is_deactivate: bool,
25882 },
25883}
25884
25885impl EventEmitter<EditorEvent> for Editor {}
25886
25887impl Focusable for Editor {
25888 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25889 self.focus_handle.clone()
25890 }
25891}
25892
25893impl Render for Editor {
25894 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25895 EditorElement::new(&cx.entity(), self.create_style(cx))
25896 }
25897}
25898
25899impl EntityInputHandler for Editor {
25900 fn text_for_range(
25901 &mut self,
25902 range_utf16: Range<usize>,
25903 adjusted_range: &mut Option<Range<usize>>,
25904 _: &mut Window,
25905 cx: &mut Context<Self>,
25906 ) -> Option<String> {
25907 let snapshot = self.buffer.read(cx).read(cx);
25908 let start = snapshot.clip_offset_utf16(
25909 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25910 Bias::Left,
25911 );
25912 let end = snapshot.clip_offset_utf16(
25913 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25914 Bias::Right,
25915 );
25916 if (start.0.0..end.0.0) != range_utf16 {
25917 adjusted_range.replace(start.0.0..end.0.0);
25918 }
25919 Some(snapshot.text_for_range(start..end).collect())
25920 }
25921
25922 fn selected_text_range(
25923 &mut self,
25924 ignore_disabled_input: bool,
25925 _: &mut Window,
25926 cx: &mut Context<Self>,
25927 ) -> Option<UTF16Selection> {
25928 // Prevent the IME menu from appearing when holding down an alphabetic key
25929 // while input is disabled.
25930 if !ignore_disabled_input && !self.input_enabled {
25931 return None;
25932 }
25933
25934 let selection = self
25935 .selections
25936 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25937 let range = selection.range();
25938
25939 Some(UTF16Selection {
25940 range: range.start.0.0..range.end.0.0,
25941 reversed: selection.reversed,
25942 })
25943 }
25944
25945 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25946 let snapshot = self.buffer.read(cx).read(cx);
25947 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25948 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25949 }
25950
25951 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25952 self.clear_highlights::<InputComposition>(cx);
25953 self.ime_transaction.take();
25954 }
25955
25956 fn replace_text_in_range(
25957 &mut self,
25958 range_utf16: Option<Range<usize>>,
25959 text: &str,
25960 window: &mut Window,
25961 cx: &mut Context<Self>,
25962 ) {
25963 if !self.input_enabled {
25964 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25965 return;
25966 }
25967
25968 self.transact(window, cx, |this, window, cx| {
25969 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25970 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25971 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25972 Some(this.selection_replacement_ranges(range_utf16, cx))
25973 } else {
25974 this.marked_text_ranges(cx)
25975 };
25976
25977 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25978 let newest_selection_id = this.selections.newest_anchor().id;
25979 this.selections
25980 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25981 .iter()
25982 .zip(ranges_to_replace.iter())
25983 .find_map(|(selection, range)| {
25984 if selection.id == newest_selection_id {
25985 Some(
25986 (range.start.0.0 as isize - selection.head().0.0 as isize)
25987 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25988 )
25989 } else {
25990 None
25991 }
25992 })
25993 });
25994
25995 cx.emit(EditorEvent::InputHandled {
25996 utf16_range_to_replace: range_to_replace,
25997 text: text.into(),
25998 });
25999
26000 if let Some(new_selected_ranges) = new_selected_ranges {
26001 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
26002 selections.select_ranges(new_selected_ranges)
26003 });
26004 this.backspace(&Default::default(), window, cx);
26005 }
26006
26007 this.handle_input(text, window, cx);
26008 });
26009
26010 if let Some(transaction) = self.ime_transaction {
26011 self.buffer.update(cx, |buffer, cx| {
26012 buffer.group_until_transaction(transaction, cx);
26013 });
26014 }
26015
26016 self.unmark_text(window, cx);
26017 }
26018
26019 fn replace_and_mark_text_in_range(
26020 &mut self,
26021 range_utf16: Option<Range<usize>>,
26022 text: &str,
26023 new_selected_range_utf16: Option<Range<usize>>,
26024 window: &mut Window,
26025 cx: &mut Context<Self>,
26026 ) {
26027 if !self.input_enabled {
26028 return;
26029 }
26030
26031 let transaction = self.transact(window, cx, |this, window, cx| {
26032 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
26033 let snapshot = this.buffer.read(cx).read(cx);
26034 if let Some(relative_range_utf16) = range_utf16.as_ref() {
26035 for marked_range in &mut marked_ranges {
26036 marked_range.end = marked_range.start + relative_range_utf16.end;
26037 marked_range.start += relative_range_utf16.start;
26038 marked_range.start =
26039 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
26040 marked_range.end =
26041 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
26042 }
26043 }
26044 Some(marked_ranges)
26045 } else if let Some(range_utf16) = range_utf16 {
26046 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
26047 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
26048 Some(this.selection_replacement_ranges(range_utf16, cx))
26049 } else {
26050 None
26051 };
26052
26053 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
26054 let newest_selection_id = this.selections.newest_anchor().id;
26055 this.selections
26056 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
26057 .iter()
26058 .zip(ranges_to_replace.iter())
26059 .find_map(|(selection, range)| {
26060 if selection.id == newest_selection_id {
26061 Some(
26062 (range.start.0.0 as isize - selection.head().0.0 as isize)
26063 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
26064 )
26065 } else {
26066 None
26067 }
26068 })
26069 });
26070
26071 cx.emit(EditorEvent::InputHandled {
26072 utf16_range_to_replace: range_to_replace,
26073 text: text.into(),
26074 });
26075
26076 if let Some(ranges) = ranges_to_replace {
26077 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26078 s.select_ranges(ranges)
26079 });
26080 }
26081
26082 let marked_ranges = {
26083 let snapshot = this.buffer.read(cx).read(cx);
26084 this.selections
26085 .disjoint_anchors_arc()
26086 .iter()
26087 .map(|selection| {
26088 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
26089 })
26090 .collect::<Vec<_>>()
26091 };
26092
26093 if text.is_empty() {
26094 this.unmark_text(window, cx);
26095 } else {
26096 this.highlight_text::<InputComposition>(
26097 marked_ranges.clone(),
26098 HighlightStyle {
26099 underline: Some(UnderlineStyle {
26100 thickness: px(1.),
26101 color: None,
26102 wavy: false,
26103 }),
26104 ..Default::default()
26105 },
26106 cx,
26107 );
26108 }
26109
26110 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
26111 let use_autoclose = this.use_autoclose;
26112 let use_auto_surround = this.use_auto_surround;
26113 this.set_use_autoclose(false);
26114 this.set_use_auto_surround(false);
26115 this.handle_input(text, window, cx);
26116 this.set_use_autoclose(use_autoclose);
26117 this.set_use_auto_surround(use_auto_surround);
26118
26119 if let Some(new_selected_range) = new_selected_range_utf16 {
26120 let snapshot = this.buffer.read(cx).read(cx);
26121 let new_selected_ranges = marked_ranges
26122 .into_iter()
26123 .map(|marked_range| {
26124 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
26125 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
26126 insertion_start.0 + new_selected_range.start,
26127 ));
26128 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
26129 insertion_start.0 + new_selected_range.end,
26130 ));
26131 snapshot.clip_offset_utf16(new_start, Bias::Left)
26132 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
26133 })
26134 .collect::<Vec<_>>();
26135
26136 drop(snapshot);
26137 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
26138 selections.select_ranges(new_selected_ranges)
26139 });
26140 }
26141 });
26142
26143 self.ime_transaction = self.ime_transaction.or(transaction);
26144 if let Some(transaction) = self.ime_transaction {
26145 self.buffer.update(cx, |buffer, cx| {
26146 buffer.group_until_transaction(transaction, cx);
26147 });
26148 }
26149
26150 if self.text_highlights::<InputComposition>(cx).is_none() {
26151 self.ime_transaction.take();
26152 }
26153 }
26154
26155 fn bounds_for_range(
26156 &mut self,
26157 range_utf16: Range<usize>,
26158 element_bounds: gpui::Bounds<Pixels>,
26159 window: &mut Window,
26160 cx: &mut Context<Self>,
26161 ) -> Option<gpui::Bounds<Pixels>> {
26162 let text_layout_details = self.text_layout_details(window);
26163 let CharacterDimensions {
26164 em_width,
26165 em_advance,
26166 line_height,
26167 } = self.character_dimensions(window);
26168
26169 let snapshot = self.snapshot(window, cx);
26170 let scroll_position = snapshot.scroll_position();
26171 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
26172
26173 let start =
26174 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
26175 let x = Pixels::from(
26176 ScrollOffset::from(
26177 snapshot.x_for_display_point(start, &text_layout_details)
26178 + self.gutter_dimensions.full_width(),
26179 ) - scroll_left,
26180 );
26181 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
26182
26183 Some(Bounds {
26184 origin: element_bounds.origin + point(x, y),
26185 size: size(em_width, line_height),
26186 })
26187 }
26188
26189 fn character_index_for_point(
26190 &mut self,
26191 point: gpui::Point<Pixels>,
26192 _window: &mut Window,
26193 _cx: &mut Context<Self>,
26194 ) -> Option<usize> {
26195 let position_map = self.last_position_map.as_ref()?;
26196 if !position_map.text_hitbox.contains(&point) {
26197 return None;
26198 }
26199 let display_point = position_map.point_for_position(point).previous_valid;
26200 let anchor = position_map
26201 .snapshot
26202 .display_point_to_anchor(display_point, Bias::Left);
26203 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
26204 Some(utf16_offset.0.0)
26205 }
26206
26207 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
26208 self.input_enabled
26209 }
26210}
26211
26212trait SelectionExt {
26213 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
26214 fn spanned_rows(
26215 &self,
26216 include_end_if_at_line_start: bool,
26217 map: &DisplaySnapshot,
26218 ) -> Range<MultiBufferRow>;
26219}
26220
26221impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
26222 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
26223 let start = self
26224 .start
26225 .to_point(map.buffer_snapshot())
26226 .to_display_point(map);
26227 let end = self
26228 .end
26229 .to_point(map.buffer_snapshot())
26230 .to_display_point(map);
26231 if self.reversed {
26232 end..start
26233 } else {
26234 start..end
26235 }
26236 }
26237
26238 fn spanned_rows(
26239 &self,
26240 include_end_if_at_line_start: bool,
26241 map: &DisplaySnapshot,
26242 ) -> Range<MultiBufferRow> {
26243 let start = self.start.to_point(map.buffer_snapshot());
26244 let mut end = self.end.to_point(map.buffer_snapshot());
26245 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
26246 end.row -= 1;
26247 }
26248
26249 let buffer_start = map.prev_line_boundary(start).0;
26250 let buffer_end = map.next_line_boundary(end).0;
26251 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
26252 }
26253}
26254
26255impl<T: InvalidationRegion> InvalidationStack<T> {
26256 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
26257 where
26258 S: Clone + ToOffset,
26259 {
26260 while let Some(region) = self.last() {
26261 let all_selections_inside_invalidation_ranges =
26262 if selections.len() == region.ranges().len() {
26263 selections
26264 .iter()
26265 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
26266 .all(|(selection, invalidation_range)| {
26267 let head = selection.head().to_offset(buffer);
26268 invalidation_range.start <= head && invalidation_range.end >= head
26269 })
26270 } else {
26271 false
26272 };
26273
26274 if all_selections_inside_invalidation_ranges {
26275 break;
26276 } else {
26277 self.pop();
26278 }
26279 }
26280 }
26281}
26282
26283impl<T> Default for InvalidationStack<T> {
26284 fn default() -> Self {
26285 Self(Default::default())
26286 }
26287}
26288
26289impl<T> Deref for InvalidationStack<T> {
26290 type Target = Vec<T>;
26291
26292 fn deref(&self) -> &Self::Target {
26293 &self.0
26294 }
26295}
26296
26297impl<T> DerefMut for InvalidationStack<T> {
26298 fn deref_mut(&mut self) -> &mut Self::Target {
26299 &mut self.0
26300 }
26301}
26302
26303impl InvalidationRegion for SnippetState {
26304 fn ranges(&self) -> &[Range<Anchor>] {
26305 &self.ranges[self.active_index]
26306 }
26307}
26308
26309fn edit_prediction_edit_text(
26310 current_snapshot: &BufferSnapshot,
26311 edits: &[(Range<Anchor>, impl AsRef<str>)],
26312 edit_preview: &EditPreview,
26313 include_deletions: bool,
26314 cx: &App,
26315) -> HighlightedText {
26316 let edits = edits
26317 .iter()
26318 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
26319 .collect::<Vec<_>>();
26320
26321 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
26322}
26323
26324fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
26325 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
26326 // Just show the raw edit text with basic styling
26327 let mut text = String::new();
26328 let mut highlights = Vec::new();
26329
26330 let insertion_highlight_style = HighlightStyle {
26331 color: Some(cx.theme().colors().text),
26332 ..Default::default()
26333 };
26334
26335 for (_, edit_text) in edits {
26336 let start_offset = text.len();
26337 text.push_str(edit_text);
26338 let end_offset = text.len();
26339
26340 if start_offset < end_offset {
26341 highlights.push((start_offset..end_offset, insertion_highlight_style));
26342 }
26343 }
26344
26345 HighlightedText {
26346 text: text.into(),
26347 highlights,
26348 }
26349}
26350
26351pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
26352 match severity {
26353 lsp::DiagnosticSeverity::ERROR => colors.error,
26354 lsp::DiagnosticSeverity::WARNING => colors.warning,
26355 lsp::DiagnosticSeverity::INFORMATION => colors.info,
26356 lsp::DiagnosticSeverity::HINT => colors.info,
26357 _ => colors.ignored,
26358 }
26359}
26360
26361pub fn styled_runs_for_code_label<'a>(
26362 label: &'a CodeLabel,
26363 syntax_theme: &'a theme::SyntaxTheme,
26364 local_player: &'a theme::PlayerColor,
26365) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
26366 let fade_out = HighlightStyle {
26367 fade_out: Some(0.35),
26368 ..Default::default()
26369 };
26370
26371 let mut prev_end = label.filter_range.end;
26372 label
26373 .runs
26374 .iter()
26375 .enumerate()
26376 .flat_map(move |(ix, (range, highlight_id))| {
26377 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
26378 HighlightStyle {
26379 color: Some(local_player.cursor),
26380 ..Default::default()
26381 }
26382 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
26383 HighlightStyle {
26384 background_color: Some(local_player.selection),
26385 ..Default::default()
26386 }
26387 } else if let Some(style) = highlight_id.style(syntax_theme) {
26388 style
26389 } else {
26390 return Default::default();
26391 };
26392 let muted_style = style.highlight(fade_out);
26393
26394 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
26395 if range.start >= label.filter_range.end {
26396 if range.start > prev_end {
26397 runs.push((prev_end..range.start, fade_out));
26398 }
26399 runs.push((range.clone(), muted_style));
26400 } else if range.end <= label.filter_range.end {
26401 runs.push((range.clone(), style));
26402 } else {
26403 runs.push((range.start..label.filter_range.end, style));
26404 runs.push((label.filter_range.end..range.end, muted_style));
26405 }
26406 prev_end = cmp::max(prev_end, range.end);
26407
26408 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
26409 runs.push((prev_end..label.text.len(), fade_out));
26410 }
26411
26412 runs
26413 })
26414}
26415
26416pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
26417 let mut prev_index = 0;
26418 let mut prev_codepoint: Option<char> = None;
26419 text.char_indices()
26420 .chain([(text.len(), '\0')])
26421 .filter_map(move |(index, codepoint)| {
26422 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26423 let is_boundary = index == text.len()
26424 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
26425 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
26426 if is_boundary {
26427 let chunk = &text[prev_index..index];
26428 prev_index = index;
26429 Some(chunk)
26430 } else {
26431 None
26432 }
26433 })
26434}
26435
26436/// Given a string of text immediately before the cursor, iterates over possible
26437/// strings a snippet could match to. More precisely: returns an iterator over
26438/// suffixes of `text` created by splitting at word boundaries (before & after
26439/// every non-word character).
26440///
26441/// Shorter suffixes are returned first.
26442pub(crate) fn snippet_candidate_suffixes(
26443 text: &str,
26444 is_word_char: impl Fn(char) -> bool,
26445) -> impl std::iter::Iterator<Item = &str> {
26446 let mut prev_index = text.len();
26447 let mut prev_codepoint = None;
26448 text.char_indices()
26449 .rev()
26450 .chain([(0, '\0')])
26451 .filter_map(move |(index, codepoint)| {
26452 let prev_index = std::mem::replace(&mut prev_index, index);
26453 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26454 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
26455 None
26456 } else {
26457 let chunk = &text[prev_index..]; // go to end of string
26458 Some(chunk)
26459 }
26460 })
26461}
26462
26463pub trait RangeToAnchorExt: Sized {
26464 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
26465
26466 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
26467 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
26468 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
26469 }
26470}
26471
26472impl<T: ToOffset> RangeToAnchorExt for Range<T> {
26473 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
26474 let start_offset = self.start.to_offset(snapshot);
26475 let end_offset = self.end.to_offset(snapshot);
26476 if start_offset == end_offset {
26477 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
26478 } else {
26479 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
26480 }
26481 }
26482}
26483
26484pub trait RowExt {
26485 fn as_f64(&self) -> f64;
26486
26487 fn next_row(&self) -> Self;
26488
26489 fn previous_row(&self) -> Self;
26490
26491 fn minus(&self, other: Self) -> u32;
26492}
26493
26494impl RowExt for DisplayRow {
26495 fn as_f64(&self) -> f64 {
26496 self.0 as _
26497 }
26498
26499 fn next_row(&self) -> Self {
26500 Self(self.0 + 1)
26501 }
26502
26503 fn previous_row(&self) -> Self {
26504 Self(self.0.saturating_sub(1))
26505 }
26506
26507 fn minus(&self, other: Self) -> u32 {
26508 self.0 - other.0
26509 }
26510}
26511
26512impl RowExt for MultiBufferRow {
26513 fn as_f64(&self) -> f64 {
26514 self.0 as _
26515 }
26516
26517 fn next_row(&self) -> Self {
26518 Self(self.0 + 1)
26519 }
26520
26521 fn previous_row(&self) -> Self {
26522 Self(self.0.saturating_sub(1))
26523 }
26524
26525 fn minus(&self, other: Self) -> u32 {
26526 self.0 - other.0
26527 }
26528}
26529
26530trait RowRangeExt {
26531 type Row;
26532
26533 fn len(&self) -> usize;
26534
26535 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
26536}
26537
26538impl RowRangeExt for Range<MultiBufferRow> {
26539 type Row = MultiBufferRow;
26540
26541 fn len(&self) -> usize {
26542 (self.end.0 - self.start.0) as usize
26543 }
26544
26545 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
26546 (self.start.0..self.end.0).map(MultiBufferRow)
26547 }
26548}
26549
26550impl RowRangeExt for Range<DisplayRow> {
26551 type Row = DisplayRow;
26552
26553 fn len(&self) -> usize {
26554 (self.end.0 - self.start.0) as usize
26555 }
26556
26557 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
26558 (self.start.0..self.end.0).map(DisplayRow)
26559 }
26560}
26561
26562/// If select range has more than one line, we
26563/// just point the cursor to range.start.
26564fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
26565 if range.start.row == range.end.row {
26566 range
26567 } else {
26568 range.start..range.start
26569 }
26570}
26571pub struct KillRing(ClipboardItem);
26572impl Global for KillRing {}
26573
26574const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
26575
26576enum BreakpointPromptEditAction {
26577 Log,
26578 Condition,
26579 HitCondition,
26580}
26581
26582struct BreakpointPromptEditor {
26583 pub(crate) prompt: Entity<Editor>,
26584 editor: WeakEntity<Editor>,
26585 breakpoint_anchor: Anchor,
26586 breakpoint: Breakpoint,
26587 edit_action: BreakpointPromptEditAction,
26588 block_ids: HashSet<CustomBlockId>,
26589 editor_margins: Arc<Mutex<EditorMargins>>,
26590 _subscriptions: Vec<Subscription>,
26591}
26592
26593impl BreakpointPromptEditor {
26594 const MAX_LINES: u8 = 4;
26595
26596 fn new(
26597 editor: WeakEntity<Editor>,
26598 breakpoint_anchor: Anchor,
26599 breakpoint: Breakpoint,
26600 edit_action: BreakpointPromptEditAction,
26601 window: &mut Window,
26602 cx: &mut Context<Self>,
26603 ) -> Self {
26604 let base_text = match edit_action {
26605 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
26606 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
26607 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
26608 }
26609 .map(|msg| msg.to_string())
26610 .unwrap_or_default();
26611
26612 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
26613 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26614
26615 let prompt = cx.new(|cx| {
26616 let mut prompt = Editor::new(
26617 EditorMode::AutoHeight {
26618 min_lines: 1,
26619 max_lines: Some(Self::MAX_LINES as usize),
26620 },
26621 buffer,
26622 None,
26623 window,
26624 cx,
26625 );
26626 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
26627 prompt.set_show_cursor_when_unfocused(false, cx);
26628 prompt.set_placeholder_text(
26629 match edit_action {
26630 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
26631 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
26632 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
26633 },
26634 window,
26635 cx,
26636 );
26637
26638 prompt
26639 });
26640
26641 Self {
26642 prompt,
26643 editor,
26644 breakpoint_anchor,
26645 breakpoint,
26646 edit_action,
26647 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
26648 block_ids: Default::default(),
26649 _subscriptions: vec![],
26650 }
26651 }
26652
26653 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
26654 self.block_ids.extend(block_ids)
26655 }
26656
26657 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
26658 if let Some(editor) = self.editor.upgrade() {
26659 let message = self
26660 .prompt
26661 .read(cx)
26662 .buffer
26663 .read(cx)
26664 .as_singleton()
26665 .expect("A multi buffer in breakpoint prompt isn't possible")
26666 .read(cx)
26667 .as_rope()
26668 .to_string();
26669
26670 editor.update(cx, |editor, cx| {
26671 editor.edit_breakpoint_at_anchor(
26672 self.breakpoint_anchor,
26673 self.breakpoint.clone(),
26674 match self.edit_action {
26675 BreakpointPromptEditAction::Log => {
26676 BreakpointEditAction::EditLogMessage(message.into())
26677 }
26678 BreakpointPromptEditAction::Condition => {
26679 BreakpointEditAction::EditCondition(message.into())
26680 }
26681 BreakpointPromptEditAction::HitCondition => {
26682 BreakpointEditAction::EditHitCondition(message.into())
26683 }
26684 },
26685 cx,
26686 );
26687
26688 editor.remove_blocks(self.block_ids.clone(), None, cx);
26689 cx.focus_self(window);
26690 });
26691 }
26692 }
26693
26694 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
26695 self.editor
26696 .update(cx, |editor, cx| {
26697 editor.remove_blocks(self.block_ids.clone(), None, cx);
26698 window.focus(&editor.focus_handle, cx);
26699 })
26700 .log_err();
26701 }
26702
26703 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
26704 let settings = ThemeSettings::get_global(cx);
26705 let text_style = TextStyle {
26706 color: if self.prompt.read(cx).read_only(cx) {
26707 cx.theme().colors().text_disabled
26708 } else {
26709 cx.theme().colors().text
26710 },
26711 font_family: settings.buffer_font.family.clone(),
26712 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26713 font_size: settings.buffer_font_size(cx).into(),
26714 font_weight: settings.buffer_font.weight,
26715 line_height: relative(settings.buffer_line_height.value()),
26716 ..Default::default()
26717 };
26718 EditorElement::new(
26719 &self.prompt,
26720 EditorStyle {
26721 background: cx.theme().colors().editor_background,
26722 local_player: cx.theme().players().local(),
26723 text: text_style,
26724 ..Default::default()
26725 },
26726 )
26727 }
26728}
26729
26730impl Render for BreakpointPromptEditor {
26731 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26732 let editor_margins = *self.editor_margins.lock();
26733 let gutter_dimensions = editor_margins.gutter;
26734 h_flex()
26735 .key_context("Editor")
26736 .bg(cx.theme().colors().editor_background)
26737 .border_y_1()
26738 .border_color(cx.theme().status().info_border)
26739 .size_full()
26740 .py(window.line_height() / 2.5)
26741 .on_action(cx.listener(Self::confirm))
26742 .on_action(cx.listener(Self::cancel))
26743 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
26744 .child(div().flex_1().child(self.render_prompt_editor(cx)))
26745 }
26746}
26747
26748impl Focusable for BreakpointPromptEditor {
26749 fn focus_handle(&self, cx: &App) -> FocusHandle {
26750 self.prompt.focus_handle(cx)
26751 }
26752}
26753
26754fn all_edits_insertions_or_deletions(
26755 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26756 snapshot: &MultiBufferSnapshot,
26757) -> bool {
26758 let mut all_insertions = true;
26759 let mut all_deletions = true;
26760
26761 for (range, new_text) in edits.iter() {
26762 let range_is_empty = range.to_offset(snapshot).is_empty();
26763 let text_is_empty = new_text.is_empty();
26764
26765 if range_is_empty != text_is_empty {
26766 if range_is_empty {
26767 all_deletions = false;
26768 } else {
26769 all_insertions = false;
26770 }
26771 } else {
26772 return false;
26773 }
26774
26775 if !all_insertions && !all_deletions {
26776 return false;
26777 }
26778 }
26779 all_insertions || all_deletions
26780}
26781
26782struct MissingEditPredictionKeybindingTooltip;
26783
26784impl Render for MissingEditPredictionKeybindingTooltip {
26785 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26786 ui::tooltip_container(cx, |container, cx| {
26787 container
26788 .flex_shrink_0()
26789 .max_w_80()
26790 .min_h(rems_from_px(124.))
26791 .justify_between()
26792 .child(
26793 v_flex()
26794 .flex_1()
26795 .text_ui_sm(cx)
26796 .child(Label::new("Conflict with Accept Keybinding"))
26797 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26798 )
26799 .child(
26800 h_flex()
26801 .pb_1()
26802 .gap_1()
26803 .items_end()
26804 .w_full()
26805 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26806 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26807 }))
26808 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26809 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26810 })),
26811 )
26812 })
26813 }
26814}
26815
26816#[derive(Debug, Clone, Copy, PartialEq)]
26817pub struct LineHighlight {
26818 pub background: Background,
26819 pub border: Option<gpui::Hsla>,
26820 pub include_gutter: bool,
26821 pub type_id: Option<TypeId>,
26822}
26823
26824struct LineManipulationResult {
26825 pub new_text: String,
26826 pub line_count_before: usize,
26827 pub line_count_after: usize,
26828}
26829
26830fn render_diff_hunk_controls(
26831 row: u32,
26832 status: &DiffHunkStatus,
26833 hunk_range: Range<Anchor>,
26834 is_created_file: bool,
26835 line_height: Pixels,
26836 editor: &Entity<Editor>,
26837 _window: &mut Window,
26838 cx: &mut App,
26839) -> AnyElement {
26840 h_flex()
26841 .h(line_height)
26842 .mr_1()
26843 .gap_1()
26844 .px_0p5()
26845 .pb_1()
26846 .border_x_1()
26847 .border_b_1()
26848 .border_color(cx.theme().colors().border_variant)
26849 .rounded_b_lg()
26850 .bg(cx.theme().colors().editor_background)
26851 .gap_1()
26852 .block_mouse_except_scroll()
26853 .shadow_md()
26854 .child(if status.has_secondary_hunk() {
26855 Button::new(("stage", row as u64), "Stage")
26856 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26857 .tooltip({
26858 let focus_handle = editor.focus_handle(cx);
26859 move |_window, cx| {
26860 Tooltip::for_action_in(
26861 "Stage Hunk",
26862 &::git::ToggleStaged,
26863 &focus_handle,
26864 cx,
26865 )
26866 }
26867 })
26868 .on_click({
26869 let editor = editor.clone();
26870 move |_event, _window, cx| {
26871 editor.update(cx, |editor, cx| {
26872 editor.stage_or_unstage_diff_hunks(
26873 true,
26874 vec![hunk_range.start..hunk_range.start],
26875 cx,
26876 );
26877 });
26878 }
26879 })
26880 } else {
26881 Button::new(("unstage", row as u64), "Unstage")
26882 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26883 .tooltip({
26884 let focus_handle = editor.focus_handle(cx);
26885 move |_window, cx| {
26886 Tooltip::for_action_in(
26887 "Unstage Hunk",
26888 &::git::ToggleStaged,
26889 &focus_handle,
26890 cx,
26891 )
26892 }
26893 })
26894 .on_click({
26895 let editor = editor.clone();
26896 move |_event, _window, cx| {
26897 editor.update(cx, |editor, cx| {
26898 editor.stage_or_unstage_diff_hunks(
26899 false,
26900 vec![hunk_range.start..hunk_range.start],
26901 cx,
26902 );
26903 });
26904 }
26905 })
26906 })
26907 .child(
26908 Button::new(("restore", row as u64), "Restore")
26909 .tooltip({
26910 let focus_handle = editor.focus_handle(cx);
26911 move |_window, cx| {
26912 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26913 }
26914 })
26915 .on_click({
26916 let editor = editor.clone();
26917 move |_event, window, cx| {
26918 editor.update(cx, |editor, cx| {
26919 let snapshot = editor.snapshot(window, cx);
26920 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26921 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26922 });
26923 }
26924 })
26925 .disabled(is_created_file),
26926 )
26927 .when(
26928 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26929 |el| {
26930 el.child(
26931 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26932 .shape(IconButtonShape::Square)
26933 .icon_size(IconSize::Small)
26934 // .disabled(!has_multiple_hunks)
26935 .tooltip({
26936 let focus_handle = editor.focus_handle(cx);
26937 move |_window, cx| {
26938 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26939 }
26940 })
26941 .on_click({
26942 let editor = editor.clone();
26943 move |_event, window, cx| {
26944 editor.update(cx, |editor, cx| {
26945 let snapshot = editor.snapshot(window, cx);
26946 let position =
26947 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26948 editor.go_to_hunk_before_or_after_position(
26949 &snapshot,
26950 position,
26951 Direction::Next,
26952 window,
26953 cx,
26954 );
26955 editor.expand_selected_diff_hunks(cx);
26956 });
26957 }
26958 }),
26959 )
26960 .child(
26961 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26962 .shape(IconButtonShape::Square)
26963 .icon_size(IconSize::Small)
26964 // .disabled(!has_multiple_hunks)
26965 .tooltip({
26966 let focus_handle = editor.focus_handle(cx);
26967 move |_window, cx| {
26968 Tooltip::for_action_in(
26969 "Previous Hunk",
26970 &GoToPreviousHunk,
26971 &focus_handle,
26972 cx,
26973 )
26974 }
26975 })
26976 .on_click({
26977 let editor = editor.clone();
26978 move |_event, window, cx| {
26979 editor.update(cx, |editor, cx| {
26980 let snapshot = editor.snapshot(window, cx);
26981 let point =
26982 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26983 editor.go_to_hunk_before_or_after_position(
26984 &snapshot,
26985 point,
26986 Direction::Prev,
26987 window,
26988 cx,
26989 );
26990 editor.expand_selected_diff_hunks(cx);
26991 });
26992 }
26993 }),
26994 )
26995 },
26996 )
26997 .into_any_element()
26998}
26999
27000pub fn multibuffer_context_lines(cx: &App) -> u32 {
27001 EditorSettings::try_get(cx)
27002 .map(|settings| settings.excerpt_context_lines)
27003 .unwrap_or(2)
27004 .min(32)
27005}