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;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
167use smallvec::{SmallVec, smallvec};
168use snippet::Snippet;
169use std::{
170 any::{Any, TypeId},
171 borrow::Cow,
172 cell::{OnceCell, RefCell},
173 cmp::{self, Ordering, Reverse},
174 iter::{self, Peekable},
175 mem,
176 num::NonZeroU32,
177 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
178 path::{Path, PathBuf},
179 rc::Rc,
180 sync::Arc,
181 time::{Duration, Instant},
182};
183use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
184use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
185use theme::{
186 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
187 observe_buffer_font_size_adjustment,
188};
189use ui::{
190 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
191 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
192};
193use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
194use workspace::{
195 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
196 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
197 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
198 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
199 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
200 searchable::SearchEvent,
201};
202
203use crate::{
204 code_context_menus::CompletionsMenuSource,
205 editor_settings::MultiCursorModifier,
206 hover_links::{find_url, find_url_from_range},
207 inlays::{
208 InlineValueCache,
209 inlay_hints::{LspInlayHintData, inlay_hint_settings},
210 },
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 selections_collection::resolve_selections_wrapping_blocks,
213 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
214};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
231
232pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
233pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
234pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
235
236pub type RenderDiffHunkControlsFn = Arc<
237 dyn Fn(
238 u32,
239 &DiffHunkStatus,
240 Range<Anchor>,
241 bool,
242 Pixels,
243 &Entity<Editor>,
244 &mut Window,
245 &mut App,
246 ) -> AnyElement,
247>;
248
249enum ReportEditorEvent {
250 Saved { auto_saved: bool },
251 EditorOpened,
252 Closed,
253}
254
255impl ReportEditorEvent {
256 pub fn event_type(&self) -> &'static str {
257 match self {
258 Self::Saved { .. } => "Editor Saved",
259 Self::EditorOpened => "Editor Opened",
260 Self::Closed => "Editor Closed",
261 }
262 }
263}
264
265pub enum ActiveDebugLine {}
266pub enum DebugStackFrameLine {}
267enum DocumentHighlightRead {}
268enum DocumentHighlightWrite {}
269enum InputComposition {}
270pub enum PendingInput {}
271enum SelectedTextHighlight {}
272
273pub enum ConflictsOuter {}
274pub enum ConflictsOurs {}
275pub enum ConflictsTheirs {}
276pub enum ConflictsOursMarker {}
277pub enum ConflictsTheirsMarker {}
278
279#[derive(Debug, Copy, Clone, PartialEq, Eq)]
280pub enum Navigated {
281 Yes,
282 No,
283}
284
285impl Navigated {
286 pub fn from_bool(yes: bool) -> Navigated {
287 if yes { Navigated::Yes } else { Navigated::No }
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq)]
292enum DisplayDiffHunk {
293 Folded {
294 display_row: DisplayRow,
295 },
296 Unfolded {
297 is_created_file: bool,
298 diff_base_byte_range: Range<usize>,
299 display_row_range: Range<DisplayRow>,
300 multi_buffer_range: Range<Anchor>,
301 status: DiffHunkStatus,
302 },
303}
304
305pub enum HideMouseCursorOrigin {
306 TypingAction,
307 MovementAction,
308}
309
310pub fn init_settings(cx: &mut App) {
311 EditorSettings::register(cx);
312}
313
314pub fn init(cx: &mut App) {
315 init_settings(cx);
316
317 cx.set_global(GlobalBlameRenderer(Arc::new(())));
318
319 workspace::register_project_item::<Editor>(cx);
320 workspace::FollowableViewRegistry::register::<Editor>(cx);
321 workspace::register_serializable_item::<Editor>(cx);
322
323 cx.observe_new(
324 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
325 workspace.register_action(Editor::new_file);
326 workspace.register_action(Editor::new_file_split);
327 workspace.register_action(Editor::new_file_vertical);
328 workspace.register_action(Editor::new_file_horizontal);
329 workspace.register_action(Editor::cancel_language_server_work);
330 workspace.register_action(Editor::toggle_focus);
331 },
332 )
333 .detach();
334
335 cx.on_action(move |_: &workspace::NewFile, cx| {
336 let app_state = workspace::AppState::global(cx);
337 if let Some(app_state) = app_state.upgrade() {
338 workspace::open_new(
339 Default::default(),
340 app_state,
341 cx,
342 |workspace, window, cx| {
343 Editor::new_file(workspace, &Default::default(), window, cx)
344 },
345 )
346 .detach();
347 }
348 });
349 cx.on_action(move |_: &workspace::NewWindow, cx| {
350 let app_state = workspace::AppState::global(cx);
351 if let Some(app_state) = app_state.upgrade() {
352 workspace::open_new(
353 Default::default(),
354 app_state,
355 cx,
356 |workspace, window, cx| {
357 cx.activate(true);
358 Editor::new_file(workspace, &Default::default(), window, cx)
359 },
360 )
361 .detach();
362 }
363 });
364}
365
366pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
367 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
368}
369
370pub trait DiagnosticRenderer {
371 fn render_group(
372 &self,
373 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
374 buffer_id: BufferId,
375 snapshot: EditorSnapshot,
376 editor: WeakEntity<Editor>,
377 cx: &mut App,
378 ) -> Vec<BlockProperties<Anchor>>;
379
380 fn render_hover(
381 &self,
382 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
383 range: Range<Point>,
384 buffer_id: BufferId,
385 cx: &mut App,
386 ) -> Option<Entity<markdown::Markdown>>;
387
388 fn open_link(
389 &self,
390 editor: &mut Editor,
391 link: SharedString,
392 window: &mut Window,
393 cx: &mut Context<Editor>,
394 );
395}
396
397pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
398
399impl GlobalDiagnosticRenderer {
400 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
401 cx.try_global::<Self>().map(|g| g.0.clone())
402 }
403}
404
405impl gpui::Global for GlobalDiagnosticRenderer {}
406pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
407 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
408}
409
410pub struct SearchWithinRange;
411
412trait InvalidationRegion {
413 fn ranges(&self) -> &[Range<Anchor>];
414}
415
416#[derive(Clone, Debug, PartialEq)]
417pub enum SelectPhase {
418 Begin {
419 position: DisplayPoint,
420 add: bool,
421 click_count: usize,
422 },
423 BeginColumnar {
424 position: DisplayPoint,
425 reset: bool,
426 mode: ColumnarMode,
427 goal_column: u32,
428 },
429 Extend {
430 position: DisplayPoint,
431 click_count: usize,
432 },
433 Update {
434 position: DisplayPoint,
435 goal_column: u32,
436 scroll_delta: gpui::Point<f32>,
437 },
438 End,
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum ColumnarMode {
443 FromMouse,
444 FromSelection,
445}
446
447#[derive(Clone, Debug)]
448pub enum SelectMode {
449 Character,
450 Word(Range<Anchor>),
451 Line(Range<Anchor>),
452 All,
453}
454
455#[derive(Clone, PartialEq, Eq, Debug)]
456pub enum EditorMode {
457 SingleLine,
458 AutoHeight {
459 min_lines: usize,
460 max_lines: Option<usize>,
461 },
462 Full {
463 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
464 scale_ui_elements_with_buffer_font_size: bool,
465 /// When set to `true`, the editor will render a background for the active line.
466 show_active_line_background: bool,
467 /// When set to `true`, the editor's height will be determined by its content.
468 sized_by_content: bool,
469 },
470 Minimap {
471 parent: WeakEntity<Editor>,
472 },
473}
474
475impl EditorMode {
476 pub fn full() -> Self {
477 Self::Full {
478 scale_ui_elements_with_buffer_font_size: true,
479 show_active_line_background: true,
480 sized_by_content: false,
481 }
482 }
483
484 #[inline]
485 pub fn is_full(&self) -> bool {
486 matches!(self, Self::Full { .. })
487 }
488
489 #[inline]
490 pub fn is_single_line(&self) -> bool {
491 matches!(self, Self::SingleLine { .. })
492 }
493
494 #[inline]
495 fn is_minimap(&self) -> bool {
496 matches!(self, Self::Minimap { .. })
497 }
498}
499
500#[derive(Copy, Clone, Debug)]
501pub enum SoftWrap {
502 /// Prefer not to wrap at all.
503 ///
504 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
505 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
506 GitDiff,
507 /// Prefer a single line generally, unless an overly long line is encountered.
508 None,
509 /// Soft wrap lines that exceed the editor width.
510 EditorWidth,
511 /// Soft wrap lines at the preferred line length.
512 Column(u32),
513 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
514 Bounded(u32),
515}
516
517#[derive(Clone)]
518pub struct EditorStyle {
519 pub background: Hsla,
520 pub border: Hsla,
521 pub local_player: PlayerColor,
522 pub text: TextStyle,
523 pub scrollbar_width: Pixels,
524 pub syntax: Arc<SyntaxTheme>,
525 pub status: StatusColors,
526 pub inlay_hints_style: HighlightStyle,
527 pub edit_prediction_styles: EditPredictionStyles,
528 pub unnecessary_code_fade: f32,
529 pub show_underlines: bool,
530}
531
532impl Default for EditorStyle {
533 fn default() -> Self {
534 Self {
535 background: Hsla::default(),
536 border: Hsla::default(),
537 local_player: PlayerColor::default(),
538 text: TextStyle::default(),
539 scrollbar_width: Pixels::default(),
540 syntax: Default::default(),
541 // HACK: Status colors don't have a real default.
542 // We should look into removing the status colors from the editor
543 // style and retrieve them directly from the theme.
544 status: StatusColors::dark(),
545 inlay_hints_style: HighlightStyle::default(),
546 edit_prediction_styles: EditPredictionStyles {
547 insertion: HighlightStyle::default(),
548 whitespace: HighlightStyle::default(),
549 },
550 unnecessary_code_fade: Default::default(),
551 show_underlines: true,
552 }
553 }
554}
555
556pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
557 let show_background = language_settings::language_settings(None, None, cx)
558 .inlay_hints
559 .show_background;
560
561 let mut style = cx.theme().syntax().get("hint");
562
563 if style.color.is_none() {
564 style.color = Some(cx.theme().status().hint);
565 }
566
567 if !show_background {
568 style.background_color = None;
569 return style;
570 }
571
572 if style.background_color.is_none() {
573 style.background_color = Some(cx.theme().status().hint_background);
574 }
575
576 style
577}
578
579pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
580 EditPredictionStyles {
581 insertion: HighlightStyle {
582 color: Some(cx.theme().status().predictive),
583 ..HighlightStyle::default()
584 },
585 whitespace: HighlightStyle {
586 background_color: Some(cx.theme().status().created_background),
587 ..HighlightStyle::default()
588 },
589 }
590}
591
592type CompletionId = usize;
593
594pub(crate) enum EditDisplayMode {
595 TabAccept,
596 DiffPopover,
597 Inline,
598}
599
600enum EditPrediction {
601 Edit {
602 edits: Vec<(Range<Anchor>, String)>,
603 edit_preview: Option<EditPreview>,
604 display_mode: EditDisplayMode,
605 snapshot: BufferSnapshot,
606 },
607 /// Move to a specific location in the active editor
608 MoveWithin {
609 target: Anchor,
610 snapshot: BufferSnapshot,
611 },
612 /// Move to a specific location in a different editor (not the active one)
613 MoveOutside {
614 target: language::Anchor,
615 snapshot: BufferSnapshot,
616 },
617}
618
619struct EditPredictionState {
620 inlay_ids: Vec<InlayId>,
621 completion: EditPrediction,
622 completion_id: Option<SharedString>,
623 invalidation_range: Option<Range<Anchor>>,
624}
625
626enum EditPredictionSettings {
627 Disabled,
628 Enabled {
629 show_in_menu: bool,
630 preview_requires_modifier: bool,
631 },
632}
633
634enum EditPredictionHighlight {}
635
636#[derive(Debug, Clone)]
637struct InlineDiagnostic {
638 message: SharedString,
639 group_id: usize,
640 is_primary: bool,
641 start: Point,
642 severity: lsp::DiagnosticSeverity,
643}
644
645pub enum MenuEditPredictionsPolicy {
646 Never,
647 ByProvider,
648}
649
650pub enum EditPredictionPreview {
651 /// Modifier is not pressed
652 Inactive { released_too_fast: bool },
653 /// Modifier pressed
654 Active {
655 since: Instant,
656 previous_scroll_position: Option<ScrollAnchor>,
657 },
658}
659
660impl EditPredictionPreview {
661 pub fn released_too_fast(&self) -> bool {
662 match self {
663 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
664 EditPredictionPreview::Active { .. } => false,
665 }
666 }
667
668 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
669 if let EditPredictionPreview::Active {
670 previous_scroll_position,
671 ..
672 } = self
673 {
674 *previous_scroll_position = scroll_position;
675 }
676 }
677}
678
679pub struct ContextMenuOptions {
680 pub min_entries_visible: usize,
681 pub max_entries_visible: usize,
682 pub placement: Option<ContextMenuPlacement>,
683}
684
685#[derive(Debug, Clone, PartialEq, Eq)]
686pub enum ContextMenuPlacement {
687 Above,
688 Below,
689}
690
691#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
692struct EditorActionId(usize);
693
694impl EditorActionId {
695 pub fn post_inc(&mut self) -> Self {
696 let answer = self.0;
697
698 *self = Self(answer + 1);
699
700 Self(answer)
701 }
702}
703
704// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
705// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
706
707type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
708type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
709
710#[derive(Default)]
711struct ScrollbarMarkerState {
712 scrollbar_size: Size<Pixels>,
713 dirty: bool,
714 markers: Arc<[PaintQuad]>,
715 pending_refresh: Option<Task<Result<()>>>,
716}
717
718impl ScrollbarMarkerState {
719 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
720 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
721 }
722}
723
724#[derive(Clone, Copy, PartialEq, Eq)]
725pub enum MinimapVisibility {
726 Disabled,
727 Enabled {
728 /// The configuration currently present in the users settings.
729 setting_configuration: bool,
730 /// Whether to override the currently set visibility from the users setting.
731 toggle_override: bool,
732 },
733}
734
735impl MinimapVisibility {
736 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
737 if mode.is_full() {
738 Self::Enabled {
739 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
740 toggle_override: false,
741 }
742 } else {
743 Self::Disabled
744 }
745 }
746
747 fn hidden(&self) -> Self {
748 match *self {
749 Self::Enabled {
750 setting_configuration,
751 ..
752 } => Self::Enabled {
753 setting_configuration,
754 toggle_override: setting_configuration,
755 },
756 Self::Disabled => Self::Disabled,
757 }
758 }
759
760 fn disabled(&self) -> bool {
761 matches!(*self, Self::Disabled)
762 }
763
764 fn settings_visibility(&self) -> bool {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => setting_configuration,
770 _ => false,
771 }
772 }
773
774 fn visible(&self) -> bool {
775 match *self {
776 Self::Enabled {
777 setting_configuration,
778 toggle_override,
779 } => setting_configuration ^ toggle_override,
780 _ => false,
781 }
782 }
783
784 fn toggle_visibility(&self) -> Self {
785 match *self {
786 Self::Enabled {
787 toggle_override,
788 setting_configuration,
789 } => Self::Enabled {
790 setting_configuration,
791 toggle_override: !toggle_override,
792 },
793 Self::Disabled => Self::Disabled,
794 }
795 }
796}
797
798#[derive(Clone, Debug)]
799struct RunnableTasks {
800 templates: Vec<(TaskSourceKind, TaskTemplate)>,
801 offset: multi_buffer::Anchor,
802 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
803 column: u32,
804 // Values of all named captures, including those starting with '_'
805 extra_variables: HashMap<String, String>,
806 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
807 context_range: Range<BufferOffset>,
808}
809
810impl RunnableTasks {
811 fn resolve<'a>(
812 &'a self,
813 cx: &'a task::TaskContext,
814 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
815 self.templates.iter().filter_map(|(kind, template)| {
816 template
817 .resolve_task(&kind.to_id_base(), cx)
818 .map(|task| (kind.clone(), task))
819 })
820 }
821}
822
823#[derive(Clone)]
824pub struct ResolvedTasks {
825 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
826 position: Anchor,
827}
828
829#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
830struct BufferOffset(usize);
831
832/// Addons allow storing per-editor state in other crates (e.g. Vim)
833pub trait Addon: 'static {
834 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
835
836 fn render_buffer_header_controls(
837 &self,
838 _: &ExcerptInfo,
839 _: &Window,
840 _: &App,
841 ) -> Option<AnyElement> {
842 None
843 }
844
845 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
846 None
847 }
848
849 fn to_any(&self) -> &dyn std::any::Any;
850
851 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
852 None
853 }
854}
855
856struct ChangeLocation {
857 current: Option<Vec<Anchor>>,
858 original: Vec<Anchor>,
859}
860impl ChangeLocation {
861 fn locations(&self) -> &[Anchor] {
862 self.current.as_ref().unwrap_or(&self.original)
863 }
864}
865
866/// A set of caret positions, registered when the editor was edited.
867pub struct ChangeList {
868 changes: Vec<ChangeLocation>,
869 /// Currently "selected" change.
870 position: Option<usize>,
871}
872
873impl ChangeList {
874 pub fn new() -> Self {
875 Self {
876 changes: Vec::new(),
877 position: None,
878 }
879 }
880
881 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
882 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
883 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
884 if self.changes.is_empty() {
885 return None;
886 }
887
888 let prev = self.position.unwrap_or(self.changes.len());
889 let next = if direction == Direction::Prev {
890 prev.saturating_sub(count)
891 } else {
892 (prev + count).min(self.changes.len() - 1)
893 };
894 self.position = Some(next);
895 self.changes.get(next).map(|change| change.locations())
896 }
897
898 /// Adds a new change to the list, resetting the change list position.
899 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
900 self.position.take();
901 if let Some(last) = self.changes.last_mut()
902 && group
903 {
904 last.current = Some(new_positions)
905 } else {
906 self.changes.push(ChangeLocation {
907 original: new_positions,
908 current: None,
909 });
910 }
911 }
912
913 pub fn last(&self) -> Option<&[Anchor]> {
914 self.changes.last().map(|change| change.locations())
915 }
916
917 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
918 self.changes.last().map(|change| change.original.as_slice())
919 }
920
921 pub fn invert_last_group(&mut self) {
922 if let Some(last) = self.changes.last_mut()
923 && let Some(current) = last.current.as_mut()
924 {
925 mem::swap(&mut last.original, current);
926 }
927 }
928}
929
930#[derive(Clone)]
931struct InlineBlamePopoverState {
932 scroll_handle: ScrollHandle,
933 commit_message: Option<ParsedCommitMessage>,
934 markdown: Entity<Markdown>,
935}
936
937struct InlineBlamePopover {
938 position: gpui::Point<Pixels>,
939 hide_task: Option<Task<()>>,
940 popover_bounds: Option<Bounds<Pixels>>,
941 popover_state: InlineBlamePopoverState,
942 keyboard_grace: bool,
943}
944
945enum SelectionDragState {
946 /// State when no drag related activity is detected.
947 None,
948 /// State when the mouse is down on a selection that is about to be dragged.
949 ReadyToDrag {
950 selection: Selection<Anchor>,
951 click_position: gpui::Point<Pixels>,
952 mouse_down_time: Instant,
953 },
954 /// State when the mouse is dragging the selection in the editor.
955 Dragging {
956 selection: Selection<Anchor>,
957 drop_cursor: Selection<Anchor>,
958 hide_drop_cursor: bool,
959 },
960}
961
962enum ColumnarSelectionState {
963 FromMouse {
964 selection_tail: Anchor,
965 display_point: Option<DisplayPoint>,
966 },
967 FromSelection {
968 selection_tail: Anchor,
969 },
970}
971
972/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
973/// a breakpoint on them.
974#[derive(Clone, Copy, Debug, PartialEq, Eq)]
975struct PhantomBreakpointIndicator {
976 display_row: DisplayRow,
977 /// There's a small debounce between hovering over the line and showing the indicator.
978 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
979 is_active: bool,
980 collides_with_existing_breakpoint: bool,
981}
982
983/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
984///
985/// See the [module level documentation](self) for more information.
986pub struct Editor {
987 focus_handle: FocusHandle,
988 last_focused_descendant: Option<WeakFocusHandle>,
989 /// The text buffer being edited
990 buffer: Entity<MultiBuffer>,
991 /// Map of how text in the buffer should be displayed.
992 /// Handles soft wraps, folds, fake inlay text insertions, etc.
993 pub display_map: Entity<DisplayMap>,
994 placeholder_display_map: Option<Entity<DisplayMap>>,
995 pub selections: SelectionsCollection,
996 pub scroll_manager: ScrollManager,
997 /// When inline assist editors are linked, they all render cursors because
998 /// typing enters text into each of them, even the ones that aren't focused.
999 pub(crate) show_cursor_when_unfocused: bool,
1000 columnar_selection_state: Option<ColumnarSelectionState>,
1001 add_selections_state: Option<AddSelectionsState>,
1002 select_next_state: Option<SelectNextState>,
1003 select_prev_state: Option<SelectNextState>,
1004 selection_history: SelectionHistory,
1005 defer_selection_effects: bool,
1006 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1007 autoclose_regions: Vec<AutocloseRegion>,
1008 snippet_stack: InvalidationStack<SnippetState>,
1009 select_syntax_node_history: SelectSyntaxNodeHistory,
1010 ime_transaction: Option<TransactionId>,
1011 pub diagnostics_max_severity: DiagnosticSeverity,
1012 active_diagnostics: ActiveDiagnostic,
1013 show_inline_diagnostics: bool,
1014 inline_diagnostics_update: Task<()>,
1015 inline_diagnostics_enabled: bool,
1016 diagnostics_enabled: bool,
1017 word_completions_enabled: bool,
1018 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1019 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1020 hard_wrap: Option<usize>,
1021 project: Option<Entity<Project>>,
1022 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1023 completion_provider: Option<Rc<dyn CompletionProvider>>,
1024 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1025 blink_manager: Entity<BlinkManager>,
1026 show_cursor_names: bool,
1027 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1028 pub show_local_selections: bool,
1029 mode: EditorMode,
1030 show_breadcrumbs: bool,
1031 show_gutter: bool,
1032 show_scrollbars: ScrollbarAxes,
1033 minimap_visibility: MinimapVisibility,
1034 offset_content: bool,
1035 disable_expand_excerpt_buttons: bool,
1036 show_line_numbers: Option<bool>,
1037 use_relative_line_numbers: Option<bool>,
1038 show_git_diff_gutter: Option<bool>,
1039 show_code_actions: Option<bool>,
1040 show_runnables: Option<bool>,
1041 show_breakpoints: Option<bool>,
1042 show_wrap_guides: Option<bool>,
1043 show_indent_guides: Option<bool>,
1044 highlight_order: usize,
1045 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1046 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1047 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1048 scrollbar_marker_state: ScrollbarMarkerState,
1049 active_indent_guides_state: ActiveIndentGuidesState,
1050 nav_history: Option<ItemNavHistory>,
1051 context_menu: RefCell<Option<CodeContextMenu>>,
1052 context_menu_options: Option<ContextMenuOptions>,
1053 mouse_context_menu: Option<MouseContextMenu>,
1054 completion_tasks: Vec<(CompletionId, Task<()>)>,
1055 inline_blame_popover: Option<InlineBlamePopover>,
1056 inline_blame_popover_show_task: Option<Task<()>>,
1057 signature_help_state: SignatureHelpState,
1058 auto_signature_help: Option<bool>,
1059 find_all_references_task_sources: Vec<Anchor>,
1060 next_completion_id: CompletionId,
1061 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1062 code_actions_task: Option<Task<Result<()>>>,
1063 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1064 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1065 document_highlights_task: Option<Task<()>>,
1066 linked_editing_range_task: Option<Task<Option<()>>>,
1067 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1068 pending_rename: Option<RenameState>,
1069 searchable: bool,
1070 cursor_shape: CursorShape,
1071 current_line_highlight: Option<CurrentLineHighlight>,
1072 autoindent_mode: Option<AutoindentMode>,
1073 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1074 input_enabled: bool,
1075 use_modal_editing: bool,
1076 read_only: bool,
1077 leader_id: Option<CollaboratorId>,
1078 remote_id: Option<ViewId>,
1079 pub hover_state: HoverState,
1080 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1081 gutter_hovered: bool,
1082 hovered_link_state: Option<HoveredLinkState>,
1083 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1084 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1085 active_edit_prediction: Option<EditPredictionState>,
1086 /// Used to prevent flickering as the user types while the menu is open
1087 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1088 edit_prediction_settings: EditPredictionSettings,
1089 edit_predictions_hidden_for_vim_mode: bool,
1090 show_edit_predictions_override: Option<bool>,
1091 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1092 edit_prediction_preview: EditPredictionPreview,
1093 edit_prediction_indent_conflict: bool,
1094 edit_prediction_requires_modifier_in_indent_conflict: bool,
1095 next_inlay_id: usize,
1096 next_color_inlay_id: usize,
1097 _subscriptions: Vec<Subscription>,
1098 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1099 gutter_dimensions: GutterDimensions,
1100 style: Option<EditorStyle>,
1101 text_style_refinement: Option<TextStyleRefinement>,
1102 next_editor_action_id: EditorActionId,
1103 editor_actions: Rc<
1104 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1105 >,
1106 use_autoclose: bool,
1107 use_auto_surround: bool,
1108 auto_replace_emoji_shortcode: bool,
1109 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1110 show_git_blame_gutter: bool,
1111 show_git_blame_inline: bool,
1112 show_git_blame_inline_delay_task: Option<Task<()>>,
1113 git_blame_inline_enabled: bool,
1114 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1115 serialize_dirty_buffers: bool,
1116 show_selection_menu: Option<bool>,
1117 blame: Option<Entity<GitBlame>>,
1118 blame_subscription: Option<Subscription>,
1119 custom_context_menu: Option<
1120 Box<
1121 dyn 'static
1122 + Fn(
1123 &mut Self,
1124 DisplayPoint,
1125 &mut Window,
1126 &mut Context<Self>,
1127 ) -> Option<Entity<ui::ContextMenu>>,
1128 >,
1129 >,
1130 last_bounds: Option<Bounds<Pixels>>,
1131 last_position_map: Option<Rc<PositionMap>>,
1132 expect_bounds_change: Option<Bounds<Pixels>>,
1133 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1134 tasks_update_task: Option<Task<()>>,
1135 breakpoint_store: Option<Entity<BreakpointStore>>,
1136 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1137 hovered_diff_hunk_row: Option<DisplayRow>,
1138 pull_diagnostics_task: Task<()>,
1139 in_project_search: bool,
1140 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1141 breadcrumb_header: Option<String>,
1142 focused_block: Option<FocusedBlock>,
1143 next_scroll_position: NextScrollCursorCenterTopBottom,
1144 addons: HashMap<TypeId, Box<dyn Addon>>,
1145 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1146 load_diff_task: Option<Shared<Task<()>>>,
1147 /// Whether we are temporarily displaying a diff other than git's
1148 temporary_diff_override: bool,
1149 selection_mark_mode: bool,
1150 toggle_fold_multiple_buffers: Task<()>,
1151 _scroll_cursor_center_top_bottom_task: Task<()>,
1152 serialize_selections: Task<()>,
1153 serialize_folds: Task<()>,
1154 mouse_cursor_hidden: bool,
1155 minimap: Option<Entity<Self>>,
1156 hide_mouse_mode: HideMouseMode,
1157 pub change_list: ChangeList,
1158 inline_value_cache: InlineValueCache,
1159 selection_drag_state: SelectionDragState,
1160 colors: Option<LspColorData>,
1161 post_scroll_update: Task<()>,
1162 refresh_colors_task: Task<()>,
1163 inlay_hints: Option<LspInlayHintData>,
1164 folding_newlines: Task<()>,
1165 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1166}
1167
1168fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1169 if debounce_ms > 0 {
1170 Some(Duration::from_millis(debounce_ms))
1171 } else {
1172 None
1173 }
1174}
1175
1176#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1177enum NextScrollCursorCenterTopBottom {
1178 #[default]
1179 Center,
1180 Top,
1181 Bottom,
1182}
1183
1184impl NextScrollCursorCenterTopBottom {
1185 fn next(&self) -> Self {
1186 match self {
1187 Self::Center => Self::Top,
1188 Self::Top => Self::Bottom,
1189 Self::Bottom => Self::Center,
1190 }
1191 }
1192}
1193
1194#[derive(Clone)]
1195pub struct EditorSnapshot {
1196 pub mode: EditorMode,
1197 show_gutter: bool,
1198 show_line_numbers: Option<bool>,
1199 show_git_diff_gutter: Option<bool>,
1200 show_code_actions: Option<bool>,
1201 show_runnables: Option<bool>,
1202 show_breakpoints: Option<bool>,
1203 git_blame_gutter_max_author_length: Option<usize>,
1204 pub display_snapshot: DisplaySnapshot,
1205 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1206 is_focused: bool,
1207 scroll_anchor: ScrollAnchor,
1208 ongoing_scroll: OngoingScroll,
1209 current_line_highlight: CurrentLineHighlight,
1210 gutter_hovered: bool,
1211}
1212
1213#[derive(Default, Debug, Clone, Copy)]
1214pub struct GutterDimensions {
1215 pub left_padding: Pixels,
1216 pub right_padding: Pixels,
1217 pub width: Pixels,
1218 pub margin: Pixels,
1219 pub git_blame_entries_width: Option<Pixels>,
1220}
1221
1222impl GutterDimensions {
1223 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1224 Self {
1225 margin: Self::default_gutter_margin(font_id, font_size, cx),
1226 ..Default::default()
1227 }
1228 }
1229
1230 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1231 -cx.text_system().descent(font_id, font_size)
1232 }
1233 /// The full width of the space taken up by the gutter.
1234 pub fn full_width(&self) -> Pixels {
1235 self.margin + self.width
1236 }
1237
1238 /// The width of the space reserved for the fold indicators,
1239 /// use alongside 'justify_end' and `gutter_width` to
1240 /// right align content with the line numbers
1241 pub fn fold_area_width(&self) -> Pixels {
1242 self.margin + self.right_padding
1243 }
1244}
1245
1246struct CharacterDimensions {
1247 em_width: Pixels,
1248 em_advance: Pixels,
1249 line_height: Pixels,
1250}
1251
1252#[derive(Debug)]
1253pub struct RemoteSelection {
1254 pub replica_id: ReplicaId,
1255 pub selection: Selection<Anchor>,
1256 pub cursor_shape: CursorShape,
1257 pub collaborator_id: CollaboratorId,
1258 pub line_mode: bool,
1259 pub user_name: Option<SharedString>,
1260 pub color: PlayerColor,
1261}
1262
1263#[derive(Clone, Debug)]
1264struct SelectionHistoryEntry {
1265 selections: Arc<[Selection<Anchor>]>,
1266 select_next_state: Option<SelectNextState>,
1267 select_prev_state: Option<SelectNextState>,
1268 add_selections_state: Option<AddSelectionsState>,
1269}
1270
1271#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1272enum SelectionHistoryMode {
1273 Normal,
1274 Undoing,
1275 Redoing,
1276 Skipping,
1277}
1278
1279#[derive(Clone, PartialEq, Eq, Hash)]
1280struct HoveredCursor {
1281 replica_id: ReplicaId,
1282 selection_id: usize,
1283}
1284
1285impl Default for SelectionHistoryMode {
1286 fn default() -> Self {
1287 Self::Normal
1288 }
1289}
1290
1291#[derive(Debug)]
1292/// SelectionEffects controls the side-effects of updating the selection.
1293///
1294/// The default behaviour does "what you mostly want":
1295/// - it pushes to the nav history if the cursor moved by >10 lines
1296/// - it re-triggers completion requests
1297/// - it scrolls to fit
1298///
1299/// You might want to modify these behaviours. For example when doing a "jump"
1300/// like go to definition, we always want to add to nav history; but when scrolling
1301/// in vim mode we never do.
1302///
1303/// Similarly, you might want to disable scrolling if you don't want the viewport to
1304/// move.
1305#[derive(Clone)]
1306pub struct SelectionEffects {
1307 nav_history: Option<bool>,
1308 completions: bool,
1309 scroll: Option<Autoscroll>,
1310}
1311
1312impl Default for SelectionEffects {
1313 fn default() -> Self {
1314 Self {
1315 nav_history: None,
1316 completions: true,
1317 scroll: Some(Autoscroll::fit()),
1318 }
1319 }
1320}
1321impl SelectionEffects {
1322 pub fn scroll(scroll: Autoscroll) -> Self {
1323 Self {
1324 scroll: Some(scroll),
1325 ..Default::default()
1326 }
1327 }
1328
1329 pub fn no_scroll() -> Self {
1330 Self {
1331 scroll: None,
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn completions(self, completions: bool) -> Self {
1337 Self {
1338 completions,
1339 ..self
1340 }
1341 }
1342
1343 pub fn nav_history(self, nav_history: bool) -> Self {
1344 Self {
1345 nav_history: Some(nav_history),
1346 ..self
1347 }
1348 }
1349}
1350
1351struct DeferredSelectionEffectsState {
1352 changed: bool,
1353 effects: SelectionEffects,
1354 old_cursor_position: Anchor,
1355 history_entry: SelectionHistoryEntry,
1356}
1357
1358#[derive(Default)]
1359struct SelectionHistory {
1360 #[allow(clippy::type_complexity)]
1361 selections_by_transaction:
1362 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1363 mode: SelectionHistoryMode,
1364 undo_stack: VecDeque<SelectionHistoryEntry>,
1365 redo_stack: VecDeque<SelectionHistoryEntry>,
1366}
1367
1368impl SelectionHistory {
1369 #[track_caller]
1370 fn insert_transaction(
1371 &mut self,
1372 transaction_id: TransactionId,
1373 selections: Arc<[Selection<Anchor>]>,
1374 ) {
1375 if selections.is_empty() {
1376 log::error!(
1377 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1378 std::panic::Location::caller()
1379 );
1380 return;
1381 }
1382 self.selections_by_transaction
1383 .insert(transaction_id, (selections, None));
1384 }
1385
1386 #[allow(clippy::type_complexity)]
1387 fn transaction(
1388 &self,
1389 transaction_id: TransactionId,
1390 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1391 self.selections_by_transaction.get(&transaction_id)
1392 }
1393
1394 #[allow(clippy::type_complexity)]
1395 fn transaction_mut(
1396 &mut self,
1397 transaction_id: TransactionId,
1398 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1399 self.selections_by_transaction.get_mut(&transaction_id)
1400 }
1401
1402 fn push(&mut self, entry: SelectionHistoryEntry) {
1403 if !entry.selections.is_empty() {
1404 match self.mode {
1405 SelectionHistoryMode::Normal => {
1406 self.push_undo(entry);
1407 self.redo_stack.clear();
1408 }
1409 SelectionHistoryMode::Undoing => self.push_redo(entry),
1410 SelectionHistoryMode::Redoing => self.push_undo(entry),
1411 SelectionHistoryMode::Skipping => {}
1412 }
1413 }
1414 }
1415
1416 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1417 if self
1418 .undo_stack
1419 .back()
1420 .is_none_or(|e| e.selections != entry.selections)
1421 {
1422 self.undo_stack.push_back(entry);
1423 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1424 self.undo_stack.pop_front();
1425 }
1426 }
1427 }
1428
1429 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1430 if self
1431 .redo_stack
1432 .back()
1433 .is_none_or(|e| e.selections != entry.selections)
1434 {
1435 self.redo_stack.push_back(entry);
1436 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1437 self.redo_stack.pop_front();
1438 }
1439 }
1440 }
1441}
1442
1443#[derive(Clone, Copy)]
1444pub struct RowHighlightOptions {
1445 pub autoscroll: bool,
1446 pub include_gutter: bool,
1447}
1448
1449impl Default for RowHighlightOptions {
1450 fn default() -> Self {
1451 Self {
1452 autoscroll: Default::default(),
1453 include_gutter: true,
1454 }
1455 }
1456}
1457
1458struct RowHighlight {
1459 index: usize,
1460 range: Range<Anchor>,
1461 color: Hsla,
1462 options: RowHighlightOptions,
1463 type_id: TypeId,
1464}
1465
1466#[derive(Clone, Debug)]
1467struct AddSelectionsState {
1468 groups: Vec<AddSelectionsGroup>,
1469}
1470
1471#[derive(Clone, Debug)]
1472struct AddSelectionsGroup {
1473 above: bool,
1474 stack: Vec<usize>,
1475}
1476
1477#[derive(Clone)]
1478struct SelectNextState {
1479 query: AhoCorasick,
1480 wordwise: bool,
1481 done: bool,
1482}
1483
1484impl std::fmt::Debug for SelectNextState {
1485 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1486 f.debug_struct(std::any::type_name::<Self>())
1487 .field("wordwise", &self.wordwise)
1488 .field("done", &self.done)
1489 .finish()
1490 }
1491}
1492
1493#[derive(Debug)]
1494struct AutocloseRegion {
1495 selection_id: usize,
1496 range: Range<Anchor>,
1497 pair: BracketPair,
1498}
1499
1500#[derive(Debug)]
1501struct SnippetState {
1502 ranges: Vec<Vec<Range<Anchor>>>,
1503 active_index: usize,
1504 choices: Vec<Option<Vec<String>>>,
1505}
1506
1507#[doc(hidden)]
1508pub struct RenameState {
1509 pub range: Range<Anchor>,
1510 pub old_name: Arc<str>,
1511 pub editor: Entity<Editor>,
1512 block_id: CustomBlockId,
1513}
1514
1515struct InvalidationStack<T>(Vec<T>);
1516
1517struct RegisteredEditPredictionProvider {
1518 provider: Arc<dyn EditPredictionProviderHandle>,
1519 _subscription: Subscription,
1520}
1521
1522#[derive(Debug, PartialEq, Eq)]
1523pub struct ActiveDiagnosticGroup {
1524 pub active_range: Range<Anchor>,
1525 pub active_message: String,
1526 pub group_id: usize,
1527 pub blocks: HashSet<CustomBlockId>,
1528}
1529
1530#[derive(Debug, PartialEq, Eq)]
1531
1532pub(crate) enum ActiveDiagnostic {
1533 None,
1534 All,
1535 Group(ActiveDiagnosticGroup),
1536}
1537
1538#[derive(Serialize, Deserialize, Clone, Debug)]
1539pub struct ClipboardSelection {
1540 /// The number of bytes in this selection.
1541 pub len: usize,
1542 /// Whether this was a full-line selection.
1543 pub is_entire_line: bool,
1544 /// The indentation of the first line when this content was originally copied.
1545 pub first_line_indent: u32,
1546}
1547
1548// selections, scroll behavior, was newest selection reversed
1549type SelectSyntaxNodeHistoryState = (
1550 Box<[Selection<usize>]>,
1551 SelectSyntaxNodeScrollBehavior,
1552 bool,
1553);
1554
1555#[derive(Default)]
1556struct SelectSyntaxNodeHistory {
1557 stack: Vec<SelectSyntaxNodeHistoryState>,
1558 // disable temporarily to allow changing selections without losing the stack
1559 pub disable_clearing: bool,
1560}
1561
1562impl SelectSyntaxNodeHistory {
1563 pub fn try_clear(&mut self) {
1564 if !self.disable_clearing {
1565 self.stack.clear();
1566 }
1567 }
1568
1569 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1570 self.stack.push(selection);
1571 }
1572
1573 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1574 self.stack.pop()
1575 }
1576}
1577
1578enum SelectSyntaxNodeScrollBehavior {
1579 CursorTop,
1580 FitSelection,
1581 CursorBottom,
1582}
1583
1584#[derive(Debug)]
1585pub(crate) struct NavigationData {
1586 cursor_anchor: Anchor,
1587 cursor_position: Point,
1588 scroll_anchor: ScrollAnchor,
1589 scroll_top_row: u32,
1590}
1591
1592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1593pub enum GotoDefinitionKind {
1594 Symbol,
1595 Declaration,
1596 Type,
1597 Implementation,
1598}
1599
1600pub enum FormatTarget {
1601 Buffers(HashSet<Entity<Buffer>>),
1602 Ranges(Vec<Range<MultiBufferPoint>>),
1603}
1604
1605pub(crate) struct FocusedBlock {
1606 id: BlockId,
1607 focus_handle: WeakFocusHandle,
1608}
1609
1610#[derive(Clone)]
1611enum JumpData {
1612 MultiBufferRow {
1613 row: MultiBufferRow,
1614 line_offset_from_top: u32,
1615 },
1616 MultiBufferPoint {
1617 excerpt_id: ExcerptId,
1618 position: Point,
1619 anchor: text::Anchor,
1620 line_offset_from_top: u32,
1621 },
1622}
1623
1624pub enum MultibufferSelectionMode {
1625 First,
1626 All,
1627}
1628
1629#[derive(Clone, Copy, Debug, Default)]
1630pub struct RewrapOptions {
1631 pub override_language_settings: bool,
1632 pub preserve_existing_whitespace: bool,
1633}
1634
1635impl Editor {
1636 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1637 let buffer = cx.new(|cx| Buffer::local("", cx));
1638 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1639 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1640 }
1641
1642 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1643 let buffer = cx.new(|cx| Buffer::local("", cx));
1644 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1645 Self::new(EditorMode::full(), buffer, None, window, cx)
1646 }
1647
1648 pub fn auto_height(
1649 min_lines: usize,
1650 max_lines: usize,
1651 window: &mut Window,
1652 cx: &mut Context<Self>,
1653 ) -> Self {
1654 let buffer = cx.new(|cx| Buffer::local("", cx));
1655 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1656 Self::new(
1657 EditorMode::AutoHeight {
1658 min_lines,
1659 max_lines: Some(max_lines),
1660 },
1661 buffer,
1662 None,
1663 window,
1664 cx,
1665 )
1666 }
1667
1668 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1669 /// The editor grows as tall as needed to fit its content.
1670 pub fn auto_height_unbounded(
1671 min_lines: usize,
1672 window: &mut Window,
1673 cx: &mut Context<Self>,
1674 ) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(
1678 EditorMode::AutoHeight {
1679 min_lines,
1680 max_lines: None,
1681 },
1682 buffer,
1683 None,
1684 window,
1685 cx,
1686 )
1687 }
1688
1689 pub fn for_buffer(
1690 buffer: Entity<Buffer>,
1691 project: Option<Entity<Project>>,
1692 window: &mut Window,
1693 cx: &mut Context<Self>,
1694 ) -> Self {
1695 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1696 Self::new(EditorMode::full(), buffer, project, window, cx)
1697 }
1698
1699 pub fn for_multibuffer(
1700 buffer: Entity<MultiBuffer>,
1701 project: Option<Entity<Project>>,
1702 window: &mut Window,
1703 cx: &mut Context<Self>,
1704 ) -> Self {
1705 Self::new(EditorMode::full(), buffer, project, window, cx)
1706 }
1707
1708 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1709 let mut clone = Self::new(
1710 self.mode.clone(),
1711 self.buffer.clone(),
1712 self.project.clone(),
1713 window,
1714 cx,
1715 );
1716 self.display_map.update(cx, |display_map, cx| {
1717 let snapshot = display_map.snapshot(cx);
1718 clone.display_map.update(cx, |display_map, cx| {
1719 display_map.set_state(&snapshot, cx);
1720 });
1721 });
1722 clone.folds_did_change(cx);
1723 clone.selections.clone_state(&self.selections);
1724 clone.scroll_manager.clone_state(&self.scroll_manager);
1725 clone.searchable = self.searchable;
1726 clone.read_only = self.read_only;
1727 clone
1728 }
1729
1730 pub fn new(
1731 mode: EditorMode,
1732 buffer: Entity<MultiBuffer>,
1733 project: Option<Entity<Project>>,
1734 window: &mut Window,
1735 cx: &mut Context<Self>,
1736 ) -> Self {
1737 Editor::new_internal(mode, buffer, project, None, window, cx)
1738 }
1739
1740 fn new_internal(
1741 mode: EditorMode,
1742 multi_buffer: Entity<MultiBuffer>,
1743 project: Option<Entity<Project>>,
1744 display_map: Option<Entity<DisplayMap>>,
1745 window: &mut Window,
1746 cx: &mut Context<Self>,
1747 ) -> Self {
1748 debug_assert!(
1749 display_map.is_none() || mode.is_minimap(),
1750 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1751 );
1752
1753 let full_mode = mode.is_full();
1754 let is_minimap = mode.is_minimap();
1755 let diagnostics_max_severity = if full_mode {
1756 EditorSettings::get_global(cx)
1757 .diagnostics_max_severity
1758 .unwrap_or(DiagnosticSeverity::Hint)
1759 } else {
1760 DiagnosticSeverity::Off
1761 };
1762 let style = window.text_style();
1763 let font_size = style.font_size.to_pixels(window.rem_size());
1764 let editor = cx.entity().downgrade();
1765 let fold_placeholder = FoldPlaceholder {
1766 constrain_width: false,
1767 render: Arc::new(move |fold_id, fold_range, cx| {
1768 let editor = editor.clone();
1769 div()
1770 .id(fold_id)
1771 .bg(cx.theme().colors().ghost_element_background)
1772 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1773 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1774 .rounded_xs()
1775 .size_full()
1776 .cursor_pointer()
1777 .child("⋯")
1778 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1779 .on_click(move |_, _window, cx| {
1780 editor
1781 .update(cx, |editor, cx| {
1782 editor.unfold_ranges(
1783 &[fold_range.start..fold_range.end],
1784 true,
1785 false,
1786 cx,
1787 );
1788 cx.stop_propagation();
1789 })
1790 .ok();
1791 })
1792 .into_any()
1793 }),
1794 merge_adjacent: true,
1795 ..FoldPlaceholder::default()
1796 };
1797 let display_map = display_map.unwrap_or_else(|| {
1798 cx.new(|cx| {
1799 DisplayMap::new(
1800 multi_buffer.clone(),
1801 style.font(),
1802 font_size,
1803 None,
1804 FILE_HEADER_HEIGHT,
1805 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1806 fold_placeholder,
1807 diagnostics_max_severity,
1808 cx,
1809 )
1810 })
1811 });
1812
1813 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1814
1815 let blink_manager = cx.new(|cx| {
1816 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1817 if is_minimap {
1818 blink_manager.disable(cx);
1819 }
1820 blink_manager
1821 });
1822
1823 let soft_wrap_mode_override =
1824 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1825
1826 let mut project_subscriptions = Vec::new();
1827 if full_mode && let Some(project) = project.as_ref() {
1828 project_subscriptions.push(cx.subscribe_in(
1829 project,
1830 window,
1831 |editor, _, event, window, cx| match event {
1832 project::Event::RefreshCodeLens => {
1833 // we always query lens with actions, without storing them, always refreshing them
1834 }
1835 project::Event::RefreshInlayHints(server_id) => {
1836 editor.refresh_inlay_hints(
1837 InlayHintRefreshReason::RefreshRequested(*server_id),
1838 cx,
1839 );
1840 }
1841 project::Event::LanguageServerRemoved(..) => {
1842 if editor.tasks_update_task.is_none() {
1843 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1844 }
1845 editor.registered_buffers.clear();
1846 editor.register_visible_buffers(cx);
1847 }
1848 project::Event::LanguageServerAdded(..) => {
1849 if editor.tasks_update_task.is_none() {
1850 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1851 }
1852 }
1853 project::Event::SnippetEdit(id, snippet_edits) => {
1854 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1855 let focus_handle = editor.focus_handle(cx);
1856 if focus_handle.is_focused(window) {
1857 let snapshot = buffer.read(cx).snapshot();
1858 for (range, snippet) in snippet_edits {
1859 let editor_range =
1860 language::range_from_lsp(*range).to_offset(&snapshot);
1861 editor
1862 .insert_snippet(
1863 &[editor_range],
1864 snippet.clone(),
1865 window,
1866 cx,
1867 )
1868 .ok();
1869 }
1870 }
1871 }
1872 }
1873 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1874 let buffer_id = *buffer_id;
1875 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1876 editor.register_buffer(buffer_id, cx);
1877 editor.update_lsp_data(Some(buffer_id), window, cx);
1878 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1879 refresh_linked_ranges(editor, window, cx);
1880 editor.refresh_code_actions(window, cx);
1881 editor.refresh_document_highlights(cx);
1882 }
1883 }
1884
1885 project::Event::EntryRenamed(transaction) => {
1886 let Some(workspace) = editor.workspace() else {
1887 return;
1888 };
1889 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1890 else {
1891 return;
1892 };
1893 if active_editor.entity_id() == cx.entity_id() {
1894 let edited_buffers_already_open = {
1895 let other_editors: Vec<Entity<Editor>> = workspace
1896 .read(cx)
1897 .panes()
1898 .iter()
1899 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1900 .filter(|editor| editor.entity_id() != cx.entity_id())
1901 .collect();
1902
1903 transaction.0.keys().all(|buffer| {
1904 other_editors.iter().any(|editor| {
1905 let multi_buffer = editor.read(cx).buffer();
1906 multi_buffer.read(cx).is_singleton()
1907 && multi_buffer.read(cx).as_singleton().map_or(
1908 false,
1909 |singleton| {
1910 singleton.entity_id() == buffer.entity_id()
1911 },
1912 )
1913 })
1914 })
1915 };
1916
1917 if !edited_buffers_already_open {
1918 let workspace = workspace.downgrade();
1919 let transaction = transaction.clone();
1920 cx.defer_in(window, move |_, window, cx| {
1921 cx.spawn_in(window, async move |editor, cx| {
1922 Self::open_project_transaction(
1923 &editor,
1924 workspace,
1925 transaction,
1926 "Rename".to_string(),
1927 cx,
1928 )
1929 .await
1930 .ok()
1931 })
1932 .detach();
1933 });
1934 }
1935 }
1936 }
1937
1938 _ => {}
1939 },
1940 ));
1941 if let Some(task_inventory) = project
1942 .read(cx)
1943 .task_store()
1944 .read(cx)
1945 .task_inventory()
1946 .cloned()
1947 {
1948 project_subscriptions.push(cx.observe_in(
1949 &task_inventory,
1950 window,
1951 |editor, _, window, cx| {
1952 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1953 },
1954 ));
1955 };
1956
1957 project_subscriptions.push(cx.subscribe_in(
1958 &project.read(cx).breakpoint_store(),
1959 window,
1960 |editor, _, event, window, cx| match event {
1961 BreakpointStoreEvent::ClearDebugLines => {
1962 editor.clear_row_highlights::<ActiveDebugLine>();
1963 editor.refresh_inline_values(cx);
1964 }
1965 BreakpointStoreEvent::SetDebugLine => {
1966 if editor.go_to_active_debug_line(window, cx) {
1967 cx.stop_propagation();
1968 }
1969
1970 editor.refresh_inline_values(cx);
1971 }
1972 _ => {}
1973 },
1974 ));
1975 let git_store = project.read(cx).git_store().clone();
1976 let project = project.clone();
1977 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1978 if let GitStoreEvent::RepositoryAdded = event {
1979 this.load_diff_task = Some(
1980 update_uncommitted_diff_for_buffer(
1981 cx.entity(),
1982 &project,
1983 this.buffer.read(cx).all_buffers(),
1984 this.buffer.clone(),
1985 cx,
1986 )
1987 .shared(),
1988 );
1989 }
1990 }));
1991 }
1992
1993 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1994
1995 let inlay_hint_settings =
1996 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1997 let focus_handle = cx.focus_handle();
1998 if !is_minimap {
1999 cx.on_focus(&focus_handle, window, Self::handle_focus)
2000 .detach();
2001 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2002 .detach();
2003 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2004 .detach();
2005 cx.on_blur(&focus_handle, window, Self::handle_blur)
2006 .detach();
2007 cx.observe_pending_input(window, Self::observe_pending_input)
2008 .detach();
2009 }
2010
2011 let show_indent_guides =
2012 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2013 Some(false)
2014 } else {
2015 None
2016 };
2017
2018 let breakpoint_store = match (&mode, project.as_ref()) {
2019 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2020 _ => None,
2021 };
2022
2023 let mut code_action_providers = Vec::new();
2024 let mut load_uncommitted_diff = None;
2025 if let Some(project) = project.clone() {
2026 load_uncommitted_diff = Some(
2027 update_uncommitted_diff_for_buffer(
2028 cx.entity(),
2029 &project,
2030 multi_buffer.read(cx).all_buffers(),
2031 multi_buffer.clone(),
2032 cx,
2033 )
2034 .shared(),
2035 );
2036 code_action_providers.push(Rc::new(project) as Rc<_>);
2037 }
2038
2039 let mut editor = Self {
2040 focus_handle,
2041 show_cursor_when_unfocused: false,
2042 last_focused_descendant: None,
2043 buffer: multi_buffer.clone(),
2044 display_map: display_map.clone(),
2045 placeholder_display_map: None,
2046 selections,
2047 scroll_manager: ScrollManager::new(cx),
2048 columnar_selection_state: None,
2049 add_selections_state: None,
2050 select_next_state: None,
2051 select_prev_state: None,
2052 selection_history: SelectionHistory::default(),
2053 defer_selection_effects: false,
2054 deferred_selection_effects_state: None,
2055 autoclose_regions: Vec::new(),
2056 snippet_stack: InvalidationStack::default(),
2057 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2058 ime_transaction: None,
2059 active_diagnostics: ActiveDiagnostic::None,
2060 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2061 inline_diagnostics_update: Task::ready(()),
2062 inline_diagnostics: Vec::new(),
2063 soft_wrap_mode_override,
2064 diagnostics_max_severity,
2065 hard_wrap: None,
2066 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2067 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2068 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2069 project,
2070 blink_manager: blink_manager.clone(),
2071 show_local_selections: true,
2072 show_scrollbars: ScrollbarAxes {
2073 horizontal: full_mode,
2074 vertical: full_mode,
2075 },
2076 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2077 offset_content: !matches!(mode, EditorMode::SingleLine),
2078 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2079 show_gutter: full_mode,
2080 show_line_numbers: (!full_mode).then_some(false),
2081 use_relative_line_numbers: None,
2082 disable_expand_excerpt_buttons: !full_mode,
2083 show_git_diff_gutter: None,
2084 show_code_actions: None,
2085 show_runnables: None,
2086 show_breakpoints: None,
2087 show_wrap_guides: None,
2088 show_indent_guides,
2089 highlight_order: 0,
2090 highlighted_rows: HashMap::default(),
2091 background_highlights: HashMap::default(),
2092 gutter_highlights: HashMap::default(),
2093 scrollbar_marker_state: ScrollbarMarkerState::default(),
2094 active_indent_guides_state: ActiveIndentGuidesState::default(),
2095 nav_history: None,
2096 context_menu: RefCell::new(None),
2097 context_menu_options: None,
2098 mouse_context_menu: None,
2099 completion_tasks: Vec::new(),
2100 inline_blame_popover: None,
2101 inline_blame_popover_show_task: None,
2102 signature_help_state: SignatureHelpState::default(),
2103 auto_signature_help: None,
2104 find_all_references_task_sources: Vec::new(),
2105 next_completion_id: 0,
2106 next_inlay_id: 0,
2107 code_action_providers,
2108 available_code_actions: None,
2109 code_actions_task: None,
2110 quick_selection_highlight_task: None,
2111 debounced_selection_highlight_task: None,
2112 document_highlights_task: None,
2113 linked_editing_range_task: None,
2114 pending_rename: None,
2115 searchable: !is_minimap,
2116 cursor_shape: EditorSettings::get_global(cx)
2117 .cursor_shape
2118 .unwrap_or_default(),
2119 current_line_highlight: None,
2120 autoindent_mode: Some(AutoindentMode::EachLine),
2121
2122 workspace: None,
2123 input_enabled: !is_minimap,
2124 use_modal_editing: full_mode,
2125 read_only: is_minimap,
2126 use_autoclose: true,
2127 use_auto_surround: true,
2128 auto_replace_emoji_shortcode: false,
2129 jsx_tag_auto_close_enabled_in_any_buffer: false,
2130 leader_id: None,
2131 remote_id: None,
2132 hover_state: HoverState::default(),
2133 pending_mouse_down: None,
2134 hovered_link_state: None,
2135 edit_prediction_provider: None,
2136 active_edit_prediction: None,
2137 stale_edit_prediction_in_menu: None,
2138 edit_prediction_preview: EditPredictionPreview::Inactive {
2139 released_too_fast: false,
2140 },
2141 inline_diagnostics_enabled: full_mode,
2142 diagnostics_enabled: full_mode,
2143 word_completions_enabled: full_mode,
2144 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2145 gutter_hovered: false,
2146 pixel_position_of_newest_cursor: None,
2147 last_bounds: None,
2148 last_position_map: None,
2149 expect_bounds_change: None,
2150 gutter_dimensions: GutterDimensions::default(),
2151 style: None,
2152 show_cursor_names: false,
2153 hovered_cursors: HashMap::default(),
2154 next_editor_action_id: EditorActionId::default(),
2155 editor_actions: Rc::default(),
2156 edit_predictions_hidden_for_vim_mode: false,
2157 show_edit_predictions_override: None,
2158 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2159 edit_prediction_settings: EditPredictionSettings::Disabled,
2160 edit_prediction_indent_conflict: false,
2161 edit_prediction_requires_modifier_in_indent_conflict: true,
2162 custom_context_menu: None,
2163 show_git_blame_gutter: false,
2164 show_git_blame_inline: false,
2165 show_selection_menu: None,
2166 show_git_blame_inline_delay_task: None,
2167 git_blame_inline_enabled: full_mode
2168 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2169 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2170 serialize_dirty_buffers: !is_minimap
2171 && ProjectSettings::get_global(cx)
2172 .session
2173 .restore_unsaved_buffers,
2174 blame: None,
2175 blame_subscription: None,
2176 tasks: BTreeMap::default(),
2177
2178 breakpoint_store,
2179 gutter_breakpoint_indicator: (None, None),
2180 hovered_diff_hunk_row: None,
2181 _subscriptions: (!is_minimap)
2182 .then(|| {
2183 vec![
2184 cx.observe(&multi_buffer, Self::on_buffer_changed),
2185 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2186 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2187 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2188 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2189 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2190 cx.observe_window_activation(window, |editor, window, cx| {
2191 let active = window.is_window_active();
2192 editor.blink_manager.update(cx, |blink_manager, cx| {
2193 if active {
2194 blink_manager.enable(cx);
2195 } else {
2196 blink_manager.disable(cx);
2197 }
2198 });
2199 if active {
2200 editor.show_mouse_cursor(cx);
2201 }
2202 }),
2203 ]
2204 })
2205 .unwrap_or_default(),
2206 tasks_update_task: None,
2207 pull_diagnostics_task: Task::ready(()),
2208 colors: None,
2209 refresh_colors_task: Task::ready(()),
2210 inlay_hints: None,
2211 next_color_inlay_id: 0,
2212 post_scroll_update: Task::ready(()),
2213 linked_edit_ranges: Default::default(),
2214 in_project_search: false,
2215 previous_search_ranges: None,
2216 breadcrumb_header: None,
2217 focused_block: None,
2218 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2219 addons: HashMap::default(),
2220 registered_buffers: HashMap::default(),
2221 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2222 selection_mark_mode: false,
2223 toggle_fold_multiple_buffers: Task::ready(()),
2224 serialize_selections: Task::ready(()),
2225 serialize_folds: Task::ready(()),
2226 text_style_refinement: None,
2227 load_diff_task: load_uncommitted_diff,
2228 temporary_diff_override: false,
2229 mouse_cursor_hidden: false,
2230 minimap: None,
2231 hide_mouse_mode: EditorSettings::get_global(cx)
2232 .hide_mouse
2233 .unwrap_or_default(),
2234 change_list: ChangeList::new(),
2235 mode,
2236 selection_drag_state: SelectionDragState::None,
2237 folding_newlines: Task::ready(()),
2238 lookup_key: None,
2239 };
2240
2241 if is_minimap {
2242 return editor;
2243 }
2244
2245 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2246 editor
2247 ._subscriptions
2248 .push(cx.observe(breakpoints, |_, _, cx| {
2249 cx.notify();
2250 }));
2251 }
2252 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2253 editor._subscriptions.extend(project_subscriptions);
2254
2255 editor._subscriptions.push(cx.subscribe_in(
2256 &cx.entity(),
2257 window,
2258 |editor, _, e: &EditorEvent, window, cx| match e {
2259 EditorEvent::ScrollPositionChanged { local, .. } => {
2260 if *local {
2261 let new_anchor = editor.scroll_manager.anchor();
2262 let snapshot = editor.snapshot(window, cx);
2263 editor.update_restoration_data(cx, move |data| {
2264 data.scroll_position = (
2265 new_anchor.top_row(snapshot.buffer_snapshot()),
2266 new_anchor.offset,
2267 );
2268 });
2269 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2270 editor.inline_blame_popover.take();
2271 }
2272 }
2273 EditorEvent::Edited { .. } => {
2274 if vim_flavor(cx).is_none() {
2275 let display_map = editor.display_snapshot(cx);
2276 let selections = editor.selections.all_adjusted_display(&display_map);
2277 let pop_state = editor
2278 .change_list
2279 .last()
2280 .map(|previous| {
2281 previous.len() == selections.len()
2282 && previous.iter().enumerate().all(|(ix, p)| {
2283 p.to_display_point(&display_map).row()
2284 == selections[ix].head().row()
2285 })
2286 })
2287 .unwrap_or(false);
2288 let new_positions = selections
2289 .into_iter()
2290 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2291 .collect();
2292 editor
2293 .change_list
2294 .push_to_change_list(pop_state, new_positions);
2295 }
2296 }
2297 _ => (),
2298 },
2299 ));
2300
2301 if let Some(dap_store) = editor
2302 .project
2303 .as_ref()
2304 .map(|project| project.read(cx).dap_store())
2305 {
2306 let weak_editor = cx.weak_entity();
2307
2308 editor
2309 ._subscriptions
2310 .push(
2311 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2312 let session_entity = cx.entity();
2313 weak_editor
2314 .update(cx, |editor, cx| {
2315 editor._subscriptions.push(
2316 cx.subscribe(&session_entity, Self::on_debug_session_event),
2317 );
2318 })
2319 .ok();
2320 }),
2321 );
2322
2323 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2324 editor
2325 ._subscriptions
2326 .push(cx.subscribe(&session, Self::on_debug_session_event));
2327 }
2328 }
2329
2330 // skip adding the initial selection to selection history
2331 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2332 editor.end_selection(window, cx);
2333 editor.selection_history.mode = SelectionHistoryMode::Normal;
2334
2335 editor.scroll_manager.show_scrollbars(window, cx);
2336 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2337
2338 if full_mode {
2339 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2340 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2341
2342 if editor.git_blame_inline_enabled {
2343 editor.start_git_blame_inline(false, window, cx);
2344 }
2345
2346 editor.go_to_active_debug_line(window, cx);
2347
2348 editor.minimap =
2349 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2350 editor.colors = Some(LspColorData::new(cx));
2351 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2352
2353 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2354 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2355 }
2356 editor.update_lsp_data(None, window, cx);
2357 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2358 }
2359
2360 editor
2361 }
2362
2363 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2364 self.selections.display_map(cx)
2365 }
2366
2367 pub fn deploy_mouse_context_menu(
2368 &mut self,
2369 position: gpui::Point<Pixels>,
2370 context_menu: Entity<ContextMenu>,
2371 window: &mut Window,
2372 cx: &mut Context<Self>,
2373 ) {
2374 self.mouse_context_menu = Some(MouseContextMenu::new(
2375 self,
2376 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2377 context_menu,
2378 window,
2379 cx,
2380 ));
2381 }
2382
2383 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2384 self.mouse_context_menu
2385 .as_ref()
2386 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2387 }
2388
2389 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2390 if self
2391 .selections
2392 .pending_anchor()
2393 .is_some_and(|pending_selection| {
2394 let snapshot = self.buffer().read(cx).snapshot(cx);
2395 pending_selection.range().includes(range, &snapshot)
2396 })
2397 {
2398 return true;
2399 }
2400
2401 self.selections
2402 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2403 .into_iter()
2404 .any(|selection| {
2405 // This is needed to cover a corner case, if we just check for an existing
2406 // selection in the fold range, having a cursor at the start of the fold
2407 // marks it as selected. Non-empty selections don't cause this.
2408 let length = selection.end - selection.start;
2409 length > 0
2410 })
2411 }
2412
2413 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2414 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2415 }
2416
2417 fn key_context_internal(
2418 &self,
2419 has_active_edit_prediction: bool,
2420 window: &mut Window,
2421 cx: &mut App,
2422 ) -> KeyContext {
2423 let mut key_context = KeyContext::new_with_defaults();
2424 key_context.add("Editor");
2425 let mode = match self.mode {
2426 EditorMode::SingleLine => "single_line",
2427 EditorMode::AutoHeight { .. } => "auto_height",
2428 EditorMode::Minimap { .. } => "minimap",
2429 EditorMode::Full { .. } => "full",
2430 };
2431
2432 if EditorSettings::jupyter_enabled(cx) {
2433 key_context.add("jupyter");
2434 }
2435
2436 key_context.set("mode", mode);
2437 if self.pending_rename.is_some() {
2438 key_context.add("renaming");
2439 }
2440
2441 if !self.snippet_stack.is_empty() {
2442 key_context.add("in_snippet");
2443 }
2444
2445 match self.context_menu.borrow().as_ref() {
2446 Some(CodeContextMenu::Completions(menu)) => {
2447 if menu.visible() {
2448 key_context.add("menu");
2449 key_context.add("showing_completions");
2450 }
2451 }
2452 Some(CodeContextMenu::CodeActions(menu)) => {
2453 if menu.visible() {
2454 key_context.add("menu");
2455 key_context.add("showing_code_actions")
2456 }
2457 }
2458 None => {}
2459 }
2460
2461 if self.signature_help_state.has_multiple_signatures() {
2462 key_context.add("showing_signature_help");
2463 }
2464
2465 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2466 if !self.focus_handle(cx).contains_focused(window, cx)
2467 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2468 {
2469 for addon in self.addons.values() {
2470 addon.extend_key_context(&mut key_context, cx)
2471 }
2472 }
2473
2474 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2475 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2476 Some(
2477 file.full_path(cx)
2478 .extension()?
2479 .to_string_lossy()
2480 .into_owned(),
2481 )
2482 }) {
2483 key_context.set("extension", extension);
2484 }
2485 } else {
2486 key_context.add("multibuffer");
2487 }
2488
2489 if has_active_edit_prediction {
2490 if self.edit_prediction_in_conflict() {
2491 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2492 } else {
2493 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2494 key_context.add("copilot_suggestion");
2495 }
2496 }
2497
2498 if self.selection_mark_mode {
2499 key_context.add("selection_mode");
2500 }
2501
2502 let disjoint = self.selections.disjoint_anchors();
2503 let snapshot = self.snapshot(window, cx);
2504 let snapshot = snapshot.buffer_snapshot();
2505 if self.mode == EditorMode::SingleLine
2506 && let [selection] = disjoint
2507 && selection.start == selection.end
2508 && selection.end.to_offset(snapshot) == snapshot.len()
2509 {
2510 key_context.add("end_of_input");
2511 }
2512
2513 key_context
2514 }
2515
2516 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2517 self.last_bounds.as_ref()
2518 }
2519
2520 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2521 if self.mouse_cursor_hidden {
2522 self.mouse_cursor_hidden = false;
2523 cx.notify();
2524 }
2525 }
2526
2527 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2528 let hide_mouse_cursor = match origin {
2529 HideMouseCursorOrigin::TypingAction => {
2530 matches!(
2531 self.hide_mouse_mode,
2532 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2533 )
2534 }
2535 HideMouseCursorOrigin::MovementAction => {
2536 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2537 }
2538 };
2539 if self.mouse_cursor_hidden != hide_mouse_cursor {
2540 self.mouse_cursor_hidden = hide_mouse_cursor;
2541 cx.notify();
2542 }
2543 }
2544
2545 pub fn edit_prediction_in_conflict(&self) -> bool {
2546 if !self.show_edit_predictions_in_menu() {
2547 return false;
2548 }
2549
2550 let showing_completions = self
2551 .context_menu
2552 .borrow()
2553 .as_ref()
2554 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2555
2556 showing_completions
2557 || self.edit_prediction_requires_modifier()
2558 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2559 // bindings to insert tab characters.
2560 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2561 }
2562
2563 pub fn accept_edit_prediction_keybind(
2564 &self,
2565 accept_partial: bool,
2566 window: &mut Window,
2567 cx: &mut App,
2568 ) -> AcceptEditPredictionBinding {
2569 let key_context = self.key_context_internal(true, window, cx);
2570 let in_conflict = self.edit_prediction_in_conflict();
2571
2572 let bindings = if accept_partial {
2573 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2574 } else {
2575 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2576 };
2577
2578 // TODO: if the binding contains multiple keystrokes, display all of them, not
2579 // just the first one.
2580 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2581 !in_conflict
2582 || binding
2583 .keystrokes()
2584 .first()
2585 .is_some_and(|keystroke| keystroke.modifiers().modified())
2586 }))
2587 }
2588
2589 pub fn new_file(
2590 workspace: &mut Workspace,
2591 _: &workspace::NewFile,
2592 window: &mut Window,
2593 cx: &mut Context<Workspace>,
2594 ) {
2595 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2596 "Failed to create buffer",
2597 window,
2598 cx,
2599 |e, _, _| match e.error_code() {
2600 ErrorCode::RemoteUpgradeRequired => Some(format!(
2601 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2602 e.error_tag("required").unwrap_or("the latest version")
2603 )),
2604 _ => None,
2605 },
2606 );
2607 }
2608
2609 pub fn new_in_workspace(
2610 workspace: &mut Workspace,
2611 window: &mut Window,
2612 cx: &mut Context<Workspace>,
2613 ) -> Task<Result<Entity<Editor>>> {
2614 let project = workspace.project().clone();
2615 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2616
2617 cx.spawn_in(window, async move |workspace, cx| {
2618 let buffer = create.await?;
2619 workspace.update_in(cx, |workspace, window, cx| {
2620 let editor =
2621 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2622 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2623 editor
2624 })
2625 })
2626 }
2627
2628 fn new_file_vertical(
2629 workspace: &mut Workspace,
2630 _: &workspace::NewFileSplitVertical,
2631 window: &mut Window,
2632 cx: &mut Context<Workspace>,
2633 ) {
2634 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2635 }
2636
2637 fn new_file_horizontal(
2638 workspace: &mut Workspace,
2639 _: &workspace::NewFileSplitHorizontal,
2640 window: &mut Window,
2641 cx: &mut Context<Workspace>,
2642 ) {
2643 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2644 }
2645
2646 fn new_file_split(
2647 workspace: &mut Workspace,
2648 action: &workspace::NewFileSplit,
2649 window: &mut Window,
2650 cx: &mut Context<Workspace>,
2651 ) {
2652 Self::new_file_in_direction(workspace, action.0, window, cx)
2653 }
2654
2655 fn new_file_in_direction(
2656 workspace: &mut Workspace,
2657 direction: SplitDirection,
2658 window: &mut Window,
2659 cx: &mut Context<Workspace>,
2660 ) {
2661 let project = workspace.project().clone();
2662 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2663
2664 cx.spawn_in(window, async move |workspace, cx| {
2665 let buffer = create.await?;
2666 workspace.update_in(cx, move |workspace, window, cx| {
2667 workspace.split_item(
2668 direction,
2669 Box::new(
2670 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2671 ),
2672 window,
2673 cx,
2674 )
2675 })?;
2676 anyhow::Ok(())
2677 })
2678 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2679 match e.error_code() {
2680 ErrorCode::RemoteUpgradeRequired => Some(format!(
2681 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2682 e.error_tag("required").unwrap_or("the latest version")
2683 )),
2684 _ => None,
2685 }
2686 });
2687 }
2688
2689 pub fn leader_id(&self) -> Option<CollaboratorId> {
2690 self.leader_id
2691 }
2692
2693 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2694 &self.buffer
2695 }
2696
2697 pub fn project(&self) -> Option<&Entity<Project>> {
2698 self.project.as_ref()
2699 }
2700
2701 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2702 self.workspace.as_ref()?.0.upgrade()
2703 }
2704
2705 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2706 self.buffer().read(cx).title(cx)
2707 }
2708
2709 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2710 let git_blame_gutter_max_author_length = self
2711 .render_git_blame_gutter(cx)
2712 .then(|| {
2713 if let Some(blame) = self.blame.as_ref() {
2714 let max_author_length =
2715 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2716 Some(max_author_length)
2717 } else {
2718 None
2719 }
2720 })
2721 .flatten();
2722
2723 EditorSnapshot {
2724 mode: self.mode.clone(),
2725 show_gutter: self.show_gutter,
2726 show_line_numbers: self.show_line_numbers,
2727 show_git_diff_gutter: self.show_git_diff_gutter,
2728 show_code_actions: self.show_code_actions,
2729 show_runnables: self.show_runnables,
2730 show_breakpoints: self.show_breakpoints,
2731 git_blame_gutter_max_author_length,
2732 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2733 placeholder_display_snapshot: self
2734 .placeholder_display_map
2735 .as_ref()
2736 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2737 scroll_anchor: self.scroll_manager.anchor(),
2738 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2739 is_focused: self.focus_handle.is_focused(window),
2740 current_line_highlight: self
2741 .current_line_highlight
2742 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2743 gutter_hovered: self.gutter_hovered,
2744 }
2745 }
2746
2747 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2748 self.buffer.read(cx).language_at(point, cx)
2749 }
2750
2751 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2752 self.buffer.read(cx).read(cx).file_at(point).cloned()
2753 }
2754
2755 pub fn active_excerpt(
2756 &self,
2757 cx: &App,
2758 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2759 self.buffer
2760 .read(cx)
2761 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2762 }
2763
2764 pub fn mode(&self) -> &EditorMode {
2765 &self.mode
2766 }
2767
2768 pub fn set_mode(&mut self, mode: EditorMode) {
2769 self.mode = mode;
2770 }
2771
2772 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2773 self.collaboration_hub.as_deref()
2774 }
2775
2776 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2777 self.collaboration_hub = Some(hub);
2778 }
2779
2780 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2781 self.in_project_search = in_project_search;
2782 }
2783
2784 pub fn set_custom_context_menu(
2785 &mut self,
2786 f: impl 'static
2787 + Fn(
2788 &mut Self,
2789 DisplayPoint,
2790 &mut Window,
2791 &mut Context<Self>,
2792 ) -> Option<Entity<ui::ContextMenu>>,
2793 ) {
2794 self.custom_context_menu = Some(Box::new(f))
2795 }
2796
2797 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2798 self.completion_provider = provider;
2799 }
2800
2801 #[cfg(any(test, feature = "test-support"))]
2802 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2803 self.completion_provider.clone()
2804 }
2805
2806 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2807 self.semantics_provider.clone()
2808 }
2809
2810 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2811 self.semantics_provider = provider;
2812 }
2813
2814 pub fn set_edit_prediction_provider<T>(
2815 &mut self,
2816 provider: Option<Entity<T>>,
2817 window: &mut Window,
2818 cx: &mut Context<Self>,
2819 ) where
2820 T: EditPredictionProvider,
2821 {
2822 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2823 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2824 if this.focus_handle.is_focused(window) {
2825 this.update_visible_edit_prediction(window, cx);
2826 }
2827 }),
2828 provider: Arc::new(provider),
2829 });
2830 self.update_edit_prediction_settings(cx);
2831 self.refresh_edit_prediction(false, false, window, cx);
2832 }
2833
2834 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2835 self.placeholder_display_map
2836 .as_ref()
2837 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2838 }
2839
2840 pub fn set_placeholder_text(
2841 &mut self,
2842 placeholder_text: &str,
2843 window: &mut Window,
2844 cx: &mut Context<Self>,
2845 ) {
2846 let multibuffer = cx
2847 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2848
2849 let style = window.text_style();
2850
2851 self.placeholder_display_map = Some(cx.new(|cx| {
2852 DisplayMap::new(
2853 multibuffer,
2854 style.font(),
2855 style.font_size.to_pixels(window.rem_size()),
2856 None,
2857 FILE_HEADER_HEIGHT,
2858 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2859 Default::default(),
2860 DiagnosticSeverity::Off,
2861 cx,
2862 )
2863 }));
2864 cx.notify();
2865 }
2866
2867 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2868 self.cursor_shape = cursor_shape;
2869
2870 // Disrupt blink for immediate user feedback that the cursor shape has changed
2871 self.blink_manager.update(cx, BlinkManager::show_cursor);
2872
2873 cx.notify();
2874 }
2875
2876 pub fn set_current_line_highlight(
2877 &mut self,
2878 current_line_highlight: Option<CurrentLineHighlight>,
2879 ) {
2880 self.current_line_highlight = current_line_highlight;
2881 }
2882
2883 pub fn range_for_match<T: std::marker::Copy>(
2884 &self,
2885 range: &Range<T>,
2886 collapse: bool,
2887 ) -> Range<T> {
2888 if collapse {
2889 return range.start..range.start;
2890 }
2891 range.clone()
2892 }
2893
2894 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2895 if self.display_map.read(cx).clip_at_line_ends != clip {
2896 self.display_map
2897 .update(cx, |map, _| map.clip_at_line_ends = clip);
2898 }
2899 }
2900
2901 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2902 self.input_enabled = input_enabled;
2903 }
2904
2905 pub fn set_edit_predictions_hidden_for_vim_mode(
2906 &mut self,
2907 hidden: bool,
2908 window: &mut Window,
2909 cx: &mut Context<Self>,
2910 ) {
2911 if hidden != self.edit_predictions_hidden_for_vim_mode {
2912 self.edit_predictions_hidden_for_vim_mode = hidden;
2913 if hidden {
2914 self.update_visible_edit_prediction(window, cx);
2915 } else {
2916 self.refresh_edit_prediction(true, false, window, cx);
2917 }
2918 }
2919 }
2920
2921 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2922 self.menu_edit_predictions_policy = value;
2923 }
2924
2925 pub fn set_autoindent(&mut self, autoindent: bool) {
2926 if autoindent {
2927 self.autoindent_mode = Some(AutoindentMode::EachLine);
2928 } else {
2929 self.autoindent_mode = None;
2930 }
2931 }
2932
2933 pub fn read_only(&self, cx: &App) -> bool {
2934 self.read_only || self.buffer.read(cx).read_only()
2935 }
2936
2937 pub fn set_read_only(&mut self, read_only: bool) {
2938 self.read_only = read_only;
2939 }
2940
2941 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2942 self.use_autoclose = autoclose;
2943 }
2944
2945 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2946 self.use_auto_surround = auto_surround;
2947 }
2948
2949 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2950 self.auto_replace_emoji_shortcode = auto_replace;
2951 }
2952
2953 pub fn toggle_edit_predictions(
2954 &mut self,
2955 _: &ToggleEditPrediction,
2956 window: &mut Window,
2957 cx: &mut Context<Self>,
2958 ) {
2959 if self.show_edit_predictions_override.is_some() {
2960 self.set_show_edit_predictions(None, window, cx);
2961 } else {
2962 let show_edit_predictions = !self.edit_predictions_enabled();
2963 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2964 }
2965 }
2966
2967 pub fn set_show_edit_predictions(
2968 &mut self,
2969 show_edit_predictions: Option<bool>,
2970 window: &mut Window,
2971 cx: &mut Context<Self>,
2972 ) {
2973 self.show_edit_predictions_override = show_edit_predictions;
2974 self.update_edit_prediction_settings(cx);
2975
2976 if let Some(false) = show_edit_predictions {
2977 self.discard_edit_prediction(false, cx);
2978 } else {
2979 self.refresh_edit_prediction(false, true, window, cx);
2980 }
2981 }
2982
2983 fn edit_predictions_disabled_in_scope(
2984 &self,
2985 buffer: &Entity<Buffer>,
2986 buffer_position: language::Anchor,
2987 cx: &App,
2988 ) -> bool {
2989 let snapshot = buffer.read(cx).snapshot();
2990 let settings = snapshot.settings_at(buffer_position, cx);
2991
2992 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2993 return false;
2994 };
2995
2996 scope.override_name().is_some_and(|scope_name| {
2997 settings
2998 .edit_predictions_disabled_in
2999 .iter()
3000 .any(|s| s == scope_name)
3001 })
3002 }
3003
3004 pub fn set_use_modal_editing(&mut self, to: bool) {
3005 self.use_modal_editing = to;
3006 }
3007
3008 pub fn use_modal_editing(&self) -> bool {
3009 self.use_modal_editing
3010 }
3011
3012 fn selections_did_change(
3013 &mut self,
3014 local: bool,
3015 old_cursor_position: &Anchor,
3016 effects: SelectionEffects,
3017 window: &mut Window,
3018 cx: &mut Context<Self>,
3019 ) {
3020 window.invalidate_character_coordinates();
3021
3022 // Copy selections to primary selection buffer
3023 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3024 if local {
3025 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3026 let buffer_handle = self.buffer.read(cx).read(cx);
3027
3028 let mut text = String::new();
3029 for (index, selection) in selections.iter().enumerate() {
3030 let text_for_selection = buffer_handle
3031 .text_for_range(selection.start..selection.end)
3032 .collect::<String>();
3033
3034 text.push_str(&text_for_selection);
3035 if index != selections.len() - 1 {
3036 text.push('\n');
3037 }
3038 }
3039
3040 if !text.is_empty() {
3041 cx.write_to_primary(ClipboardItem::new_string(text));
3042 }
3043 }
3044
3045 let selection_anchors = self.selections.disjoint_anchors_arc();
3046
3047 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3048 self.buffer.update(cx, |buffer, cx| {
3049 buffer.set_active_selections(
3050 &selection_anchors,
3051 self.selections.line_mode(),
3052 self.cursor_shape,
3053 cx,
3054 )
3055 });
3056 }
3057 let display_map = self
3058 .display_map
3059 .update(cx, |display_map, cx| display_map.snapshot(cx));
3060 let buffer = display_map.buffer_snapshot();
3061 if self.selections.count() == 1 {
3062 self.add_selections_state = None;
3063 }
3064 self.select_next_state = None;
3065 self.select_prev_state = None;
3066 self.select_syntax_node_history.try_clear();
3067 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3068 self.snippet_stack.invalidate(&selection_anchors, buffer);
3069 self.take_rename(false, window, cx);
3070
3071 let newest_selection = self.selections.newest_anchor();
3072 let new_cursor_position = newest_selection.head();
3073 let selection_start = newest_selection.start;
3074
3075 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3076 self.push_to_nav_history(
3077 *old_cursor_position,
3078 Some(new_cursor_position.to_point(buffer)),
3079 false,
3080 effects.nav_history == Some(true),
3081 cx,
3082 );
3083 }
3084
3085 if local {
3086 if let Some(buffer_id) = new_cursor_position.buffer_id {
3087 self.register_buffer(buffer_id, cx);
3088 }
3089
3090 let mut context_menu = self.context_menu.borrow_mut();
3091 let completion_menu = match context_menu.as_ref() {
3092 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3093 Some(CodeContextMenu::CodeActions(_)) => {
3094 *context_menu = None;
3095 None
3096 }
3097 None => None,
3098 };
3099 let completion_position = completion_menu.map(|menu| menu.initial_position);
3100 drop(context_menu);
3101
3102 if effects.completions
3103 && let Some(completion_position) = completion_position
3104 {
3105 let start_offset = selection_start.to_offset(buffer);
3106 let position_matches = start_offset == completion_position.to_offset(buffer);
3107 let continue_showing = if position_matches {
3108 if self.snippet_stack.is_empty() {
3109 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3110 == Some(CharKind::Word)
3111 } else {
3112 // Snippet choices can be shown even when the cursor is in whitespace.
3113 // Dismissing the menu with actions like backspace is handled by
3114 // invalidation regions.
3115 true
3116 }
3117 } else {
3118 false
3119 };
3120
3121 if continue_showing {
3122 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3123 } else {
3124 self.hide_context_menu(window, cx);
3125 }
3126 }
3127
3128 hide_hover(self, cx);
3129
3130 if old_cursor_position.to_display_point(&display_map).row()
3131 != new_cursor_position.to_display_point(&display_map).row()
3132 {
3133 self.available_code_actions.take();
3134 }
3135 self.refresh_code_actions(window, cx);
3136 self.refresh_document_highlights(cx);
3137 refresh_linked_ranges(self, window, cx);
3138
3139 self.refresh_selected_text_highlights(false, window, cx);
3140 self.refresh_matching_bracket_highlights(window, cx);
3141 self.update_visible_edit_prediction(window, cx);
3142 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3143 self.inline_blame_popover.take();
3144 if self.git_blame_inline_enabled {
3145 self.start_inline_blame_timer(window, cx);
3146 }
3147 }
3148
3149 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3150 cx.emit(EditorEvent::SelectionsChanged { local });
3151
3152 let selections = &self.selections.disjoint_anchors_arc();
3153 if selections.len() == 1 {
3154 cx.emit(SearchEvent::ActiveMatchChanged)
3155 }
3156 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3157 let inmemory_selections = selections
3158 .iter()
3159 .map(|s| {
3160 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3161 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3162 })
3163 .collect();
3164 self.update_restoration_data(cx, |data| {
3165 data.selections = inmemory_selections;
3166 });
3167
3168 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3169 && let Some(workspace_id) =
3170 self.workspace.as_ref().and_then(|workspace| workspace.1)
3171 {
3172 let snapshot = self.buffer().read(cx).snapshot(cx);
3173 let selections = selections.clone();
3174 let background_executor = cx.background_executor().clone();
3175 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3176 self.serialize_selections = cx.background_spawn(async move {
3177 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3178 let db_selections = selections
3179 .iter()
3180 .map(|selection| {
3181 (
3182 selection.start.to_offset(&snapshot),
3183 selection.end.to_offset(&snapshot),
3184 )
3185 })
3186 .collect();
3187
3188 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3189 .await
3190 .with_context(|| {
3191 format!(
3192 "persisting editor selections for editor {editor_id}, \
3193 workspace {workspace_id:?}"
3194 )
3195 })
3196 .log_err();
3197 });
3198 }
3199 }
3200
3201 cx.notify();
3202 }
3203
3204 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3205 use text::ToOffset as _;
3206 use text::ToPoint as _;
3207
3208 if self.mode.is_minimap()
3209 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3210 {
3211 return;
3212 }
3213
3214 if !self.buffer().read(cx).is_singleton() {
3215 return;
3216 }
3217
3218 let display_snapshot = self
3219 .display_map
3220 .update(cx, |display_map, cx| display_map.snapshot(cx));
3221 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3222 return;
3223 };
3224 let inmemory_folds = display_snapshot
3225 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3226 .map(|fold| {
3227 fold.range.start.text_anchor.to_point(&snapshot)
3228 ..fold.range.end.text_anchor.to_point(&snapshot)
3229 })
3230 .collect();
3231 self.update_restoration_data(cx, |data| {
3232 data.folds = inmemory_folds;
3233 });
3234
3235 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3236 return;
3237 };
3238 let background_executor = cx.background_executor().clone();
3239 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3240 let db_folds = display_snapshot
3241 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3242 .map(|fold| {
3243 (
3244 fold.range.start.text_anchor.to_offset(&snapshot),
3245 fold.range.end.text_anchor.to_offset(&snapshot),
3246 )
3247 })
3248 .collect();
3249 self.serialize_folds = cx.background_spawn(async move {
3250 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3251 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3252 .await
3253 .with_context(|| {
3254 format!(
3255 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3256 )
3257 })
3258 .log_err();
3259 });
3260 }
3261
3262 pub fn sync_selections(
3263 &mut self,
3264 other: Entity<Editor>,
3265 cx: &mut Context<Self>,
3266 ) -> gpui::Subscription {
3267 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3268 if !other_selections.is_empty() {
3269 self.selections.change_with(cx, |selections| {
3270 selections.select_anchors(other_selections);
3271 });
3272 }
3273
3274 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3275 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3276 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3277 if other_selections.is_empty() {
3278 return;
3279 }
3280 this.selections.change_with(cx, |selections| {
3281 selections.select_anchors(other_selections);
3282 });
3283 }
3284 });
3285
3286 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3287 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3288 let these_selections = this.selections.disjoint_anchors().to_vec();
3289 if these_selections.is_empty() {
3290 return;
3291 }
3292 other.update(cx, |other_editor, cx| {
3293 other_editor.selections.change_with(cx, |selections| {
3294 selections.select_anchors(these_selections);
3295 })
3296 });
3297 }
3298 });
3299
3300 Subscription::join(other_subscription, this_subscription)
3301 }
3302
3303 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3304 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3305 /// effects of selection change occur at the end of the transaction.
3306 pub fn change_selections<R>(
3307 &mut self,
3308 effects: SelectionEffects,
3309 window: &mut Window,
3310 cx: &mut Context<Self>,
3311 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3312 ) -> R {
3313 if let Some(state) = &mut self.deferred_selection_effects_state {
3314 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3315 state.effects.completions = effects.completions;
3316 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3317 let (changed, result) = self.selections.change_with(cx, change);
3318 state.changed |= changed;
3319 return result;
3320 }
3321 let mut state = DeferredSelectionEffectsState {
3322 changed: false,
3323 effects,
3324 old_cursor_position: self.selections.newest_anchor().head(),
3325 history_entry: SelectionHistoryEntry {
3326 selections: self.selections.disjoint_anchors_arc(),
3327 select_next_state: self.select_next_state.clone(),
3328 select_prev_state: self.select_prev_state.clone(),
3329 add_selections_state: self.add_selections_state.clone(),
3330 },
3331 };
3332 let (changed, result) = self.selections.change_with(cx, change);
3333 state.changed = state.changed || changed;
3334 if self.defer_selection_effects {
3335 self.deferred_selection_effects_state = Some(state);
3336 } else {
3337 self.apply_selection_effects(state, window, cx);
3338 }
3339 result
3340 }
3341
3342 /// Defers the effects of selection change, so that the effects of multiple calls to
3343 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3344 /// to selection history and the state of popovers based on selection position aren't
3345 /// erroneously updated.
3346 pub fn with_selection_effects_deferred<R>(
3347 &mut self,
3348 window: &mut Window,
3349 cx: &mut Context<Self>,
3350 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3351 ) -> R {
3352 let already_deferred = self.defer_selection_effects;
3353 self.defer_selection_effects = true;
3354 let result = update(self, window, cx);
3355 if !already_deferred {
3356 self.defer_selection_effects = false;
3357 if let Some(state) = self.deferred_selection_effects_state.take() {
3358 self.apply_selection_effects(state, window, cx);
3359 }
3360 }
3361 result
3362 }
3363
3364 fn apply_selection_effects(
3365 &mut self,
3366 state: DeferredSelectionEffectsState,
3367 window: &mut Window,
3368 cx: &mut Context<Self>,
3369 ) {
3370 if state.changed {
3371 self.selection_history.push(state.history_entry);
3372
3373 if let Some(autoscroll) = state.effects.scroll {
3374 self.request_autoscroll(autoscroll, cx);
3375 }
3376
3377 let old_cursor_position = &state.old_cursor_position;
3378
3379 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3380
3381 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3382 self.show_signature_help(&ShowSignatureHelp, window, cx);
3383 }
3384 }
3385 }
3386
3387 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3388 where
3389 I: IntoIterator<Item = (Range<S>, T)>,
3390 S: ToOffset,
3391 T: Into<Arc<str>>,
3392 {
3393 if self.read_only(cx) {
3394 return;
3395 }
3396
3397 self.buffer
3398 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3399 }
3400
3401 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3402 where
3403 I: IntoIterator<Item = (Range<S>, T)>,
3404 S: ToOffset,
3405 T: Into<Arc<str>>,
3406 {
3407 if self.read_only(cx) {
3408 return;
3409 }
3410
3411 self.buffer.update(cx, |buffer, cx| {
3412 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3413 });
3414 }
3415
3416 pub fn edit_with_block_indent<I, S, T>(
3417 &mut self,
3418 edits: I,
3419 original_indent_columns: Vec<Option<u32>>,
3420 cx: &mut Context<Self>,
3421 ) where
3422 I: IntoIterator<Item = (Range<S>, T)>,
3423 S: ToOffset,
3424 T: Into<Arc<str>>,
3425 {
3426 if self.read_only(cx) {
3427 return;
3428 }
3429
3430 self.buffer.update(cx, |buffer, cx| {
3431 buffer.edit(
3432 edits,
3433 Some(AutoindentMode::Block {
3434 original_indent_columns,
3435 }),
3436 cx,
3437 )
3438 });
3439 }
3440
3441 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3442 self.hide_context_menu(window, cx);
3443
3444 match phase {
3445 SelectPhase::Begin {
3446 position,
3447 add,
3448 click_count,
3449 } => self.begin_selection(position, add, click_count, window, cx),
3450 SelectPhase::BeginColumnar {
3451 position,
3452 goal_column,
3453 reset,
3454 mode,
3455 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3456 SelectPhase::Extend {
3457 position,
3458 click_count,
3459 } => self.extend_selection(position, click_count, window, cx),
3460 SelectPhase::Update {
3461 position,
3462 goal_column,
3463 scroll_delta,
3464 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3465 SelectPhase::End => self.end_selection(window, cx),
3466 }
3467 }
3468
3469 fn extend_selection(
3470 &mut self,
3471 position: DisplayPoint,
3472 click_count: usize,
3473 window: &mut Window,
3474 cx: &mut Context<Self>,
3475 ) {
3476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3477 let tail = self.selections.newest::<usize>(&display_map).tail();
3478 let click_count = click_count.max(match self.selections.select_mode() {
3479 SelectMode::Character => 1,
3480 SelectMode::Word(_) => 2,
3481 SelectMode::Line(_) => 3,
3482 SelectMode::All => 4,
3483 });
3484 self.begin_selection(position, false, click_count, window, cx);
3485
3486 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3487
3488 let current_selection = match self.selections.select_mode() {
3489 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3490 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3491 };
3492
3493 let mut pending_selection = self
3494 .selections
3495 .pending_anchor()
3496 .cloned()
3497 .expect("extend_selection not called with pending selection");
3498
3499 if pending_selection
3500 .start
3501 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3502 == Ordering::Greater
3503 {
3504 pending_selection.start = current_selection.start;
3505 }
3506 if pending_selection
3507 .end
3508 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3509 == Ordering::Less
3510 {
3511 pending_selection.end = current_selection.end;
3512 pending_selection.reversed = true;
3513 }
3514
3515 let mut pending_mode = self.selections.pending_mode().unwrap();
3516 match &mut pending_mode {
3517 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3518 _ => {}
3519 }
3520
3521 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3522 SelectionEffects::scroll(Autoscroll::fit())
3523 } else {
3524 SelectionEffects::no_scroll()
3525 };
3526
3527 self.change_selections(effects, window, cx, |s| {
3528 s.set_pending(pending_selection.clone(), pending_mode);
3529 s.set_is_extending(true);
3530 });
3531 }
3532
3533 fn begin_selection(
3534 &mut self,
3535 position: DisplayPoint,
3536 add: bool,
3537 click_count: usize,
3538 window: &mut Window,
3539 cx: &mut Context<Self>,
3540 ) {
3541 if !self.focus_handle.is_focused(window) {
3542 self.last_focused_descendant = None;
3543 window.focus(&self.focus_handle);
3544 }
3545
3546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3547 let buffer = display_map.buffer_snapshot();
3548 let position = display_map.clip_point(position, Bias::Left);
3549
3550 let start;
3551 let end;
3552 let mode;
3553 let mut auto_scroll;
3554 match click_count {
3555 1 => {
3556 start = buffer.anchor_before(position.to_point(&display_map));
3557 end = start;
3558 mode = SelectMode::Character;
3559 auto_scroll = true;
3560 }
3561 2 => {
3562 let position = display_map
3563 .clip_point(position, Bias::Left)
3564 .to_offset(&display_map, Bias::Left);
3565 let (range, _) = buffer.surrounding_word(position, None);
3566 start = buffer.anchor_before(range.start);
3567 end = buffer.anchor_before(range.end);
3568 mode = SelectMode::Word(start..end);
3569 auto_scroll = true;
3570 }
3571 3 => {
3572 let position = display_map
3573 .clip_point(position, Bias::Left)
3574 .to_point(&display_map);
3575 let line_start = display_map.prev_line_boundary(position).0;
3576 let next_line_start = buffer.clip_point(
3577 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3578 Bias::Left,
3579 );
3580 start = buffer.anchor_before(line_start);
3581 end = buffer.anchor_before(next_line_start);
3582 mode = SelectMode::Line(start..end);
3583 auto_scroll = true;
3584 }
3585 _ => {
3586 start = buffer.anchor_before(0);
3587 end = buffer.anchor_before(buffer.len());
3588 mode = SelectMode::All;
3589 auto_scroll = false;
3590 }
3591 }
3592 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3593
3594 let point_to_delete: Option<usize> = {
3595 let selected_points: Vec<Selection<Point>> =
3596 self.selections.disjoint_in_range(start..end, &display_map);
3597
3598 if !add || click_count > 1 {
3599 None
3600 } else if !selected_points.is_empty() {
3601 Some(selected_points[0].id)
3602 } else {
3603 let clicked_point_already_selected =
3604 self.selections.disjoint_anchors().iter().find(|selection| {
3605 selection.start.to_point(buffer) == start.to_point(buffer)
3606 || selection.end.to_point(buffer) == end.to_point(buffer)
3607 });
3608
3609 clicked_point_already_selected.map(|selection| selection.id)
3610 }
3611 };
3612
3613 let selections_count = self.selections.count();
3614 let effects = if auto_scroll {
3615 SelectionEffects::default()
3616 } else {
3617 SelectionEffects::no_scroll()
3618 };
3619
3620 self.change_selections(effects, window, cx, |s| {
3621 if let Some(point_to_delete) = point_to_delete {
3622 s.delete(point_to_delete);
3623
3624 if selections_count == 1 {
3625 s.set_pending_anchor_range(start..end, mode);
3626 }
3627 } else {
3628 if !add {
3629 s.clear_disjoint();
3630 }
3631
3632 s.set_pending_anchor_range(start..end, mode);
3633 }
3634 });
3635 }
3636
3637 fn begin_columnar_selection(
3638 &mut self,
3639 position: DisplayPoint,
3640 goal_column: u32,
3641 reset: bool,
3642 mode: ColumnarMode,
3643 window: &mut Window,
3644 cx: &mut Context<Self>,
3645 ) {
3646 if !self.focus_handle.is_focused(window) {
3647 self.last_focused_descendant = None;
3648 window.focus(&self.focus_handle);
3649 }
3650
3651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3652
3653 if reset {
3654 let pointer_position = display_map
3655 .buffer_snapshot()
3656 .anchor_before(position.to_point(&display_map));
3657
3658 self.change_selections(
3659 SelectionEffects::scroll(Autoscroll::newest()),
3660 window,
3661 cx,
3662 |s| {
3663 s.clear_disjoint();
3664 s.set_pending_anchor_range(
3665 pointer_position..pointer_position,
3666 SelectMode::Character,
3667 );
3668 },
3669 );
3670 };
3671
3672 let tail = self.selections.newest::<Point>(&display_map).tail();
3673 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3674 self.columnar_selection_state = match mode {
3675 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3676 selection_tail: selection_anchor,
3677 display_point: if reset {
3678 if position.column() != goal_column {
3679 Some(DisplayPoint::new(position.row(), goal_column))
3680 } else {
3681 None
3682 }
3683 } else {
3684 None
3685 },
3686 }),
3687 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3688 selection_tail: selection_anchor,
3689 }),
3690 };
3691
3692 if !reset {
3693 self.select_columns(position, goal_column, &display_map, window, cx);
3694 }
3695 }
3696
3697 fn update_selection(
3698 &mut self,
3699 position: DisplayPoint,
3700 goal_column: u32,
3701 scroll_delta: gpui::Point<f32>,
3702 window: &mut Window,
3703 cx: &mut Context<Self>,
3704 ) {
3705 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3706
3707 if self.columnar_selection_state.is_some() {
3708 self.select_columns(position, goal_column, &display_map, window, cx);
3709 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3710 let buffer = display_map.buffer_snapshot();
3711 let head;
3712 let tail;
3713 let mode = self.selections.pending_mode().unwrap();
3714 match &mode {
3715 SelectMode::Character => {
3716 head = position.to_point(&display_map);
3717 tail = pending.tail().to_point(buffer);
3718 }
3719 SelectMode::Word(original_range) => {
3720 let offset = display_map
3721 .clip_point(position, Bias::Left)
3722 .to_offset(&display_map, Bias::Left);
3723 let original_range = original_range.to_offset(buffer);
3724
3725 let head_offset = if buffer.is_inside_word(offset, None)
3726 || original_range.contains(&offset)
3727 {
3728 let (word_range, _) = buffer.surrounding_word(offset, None);
3729 if word_range.start < original_range.start {
3730 word_range.start
3731 } else {
3732 word_range.end
3733 }
3734 } else {
3735 offset
3736 };
3737
3738 head = head_offset.to_point(buffer);
3739 if head_offset <= original_range.start {
3740 tail = original_range.end.to_point(buffer);
3741 } else {
3742 tail = original_range.start.to_point(buffer);
3743 }
3744 }
3745 SelectMode::Line(original_range) => {
3746 let original_range = original_range.to_point(display_map.buffer_snapshot());
3747
3748 let position = display_map
3749 .clip_point(position, Bias::Left)
3750 .to_point(&display_map);
3751 let line_start = display_map.prev_line_boundary(position).0;
3752 let next_line_start = buffer.clip_point(
3753 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3754 Bias::Left,
3755 );
3756
3757 if line_start < original_range.start {
3758 head = line_start
3759 } else {
3760 head = next_line_start
3761 }
3762
3763 if head <= original_range.start {
3764 tail = original_range.end;
3765 } else {
3766 tail = original_range.start;
3767 }
3768 }
3769 SelectMode::All => {
3770 return;
3771 }
3772 };
3773
3774 if head < tail {
3775 pending.start = buffer.anchor_before(head);
3776 pending.end = buffer.anchor_before(tail);
3777 pending.reversed = true;
3778 } else {
3779 pending.start = buffer.anchor_before(tail);
3780 pending.end = buffer.anchor_before(head);
3781 pending.reversed = false;
3782 }
3783
3784 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3785 s.set_pending(pending.clone(), mode);
3786 });
3787 } else {
3788 log::error!("update_selection dispatched with no pending selection");
3789 return;
3790 }
3791
3792 self.apply_scroll_delta(scroll_delta, window, cx);
3793 cx.notify();
3794 }
3795
3796 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3797 self.columnar_selection_state.take();
3798 if let Some(pending_mode) = self.selections.pending_mode() {
3799 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3800 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3801 s.select(selections);
3802 s.clear_pending();
3803 if s.is_extending() {
3804 s.set_is_extending(false);
3805 } else {
3806 s.set_select_mode(pending_mode);
3807 }
3808 });
3809 }
3810 }
3811
3812 fn select_columns(
3813 &mut self,
3814 head: DisplayPoint,
3815 goal_column: u32,
3816 display_map: &DisplaySnapshot,
3817 window: &mut Window,
3818 cx: &mut Context<Self>,
3819 ) {
3820 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3821 return;
3822 };
3823
3824 let tail = match columnar_state {
3825 ColumnarSelectionState::FromMouse {
3826 selection_tail,
3827 display_point,
3828 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3829 ColumnarSelectionState::FromSelection { selection_tail } => {
3830 selection_tail.to_display_point(display_map)
3831 }
3832 };
3833
3834 let start_row = cmp::min(tail.row(), head.row());
3835 let end_row = cmp::max(tail.row(), head.row());
3836 let start_column = cmp::min(tail.column(), goal_column);
3837 let end_column = cmp::max(tail.column(), goal_column);
3838 let reversed = start_column < tail.column();
3839
3840 let selection_ranges = (start_row.0..=end_row.0)
3841 .map(DisplayRow)
3842 .filter_map(|row| {
3843 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3844 || start_column <= display_map.line_len(row))
3845 && !display_map.is_block_line(row)
3846 {
3847 let start = display_map
3848 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3849 .to_point(display_map);
3850 let end = display_map
3851 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3852 .to_point(display_map);
3853 if reversed {
3854 Some(end..start)
3855 } else {
3856 Some(start..end)
3857 }
3858 } else {
3859 None
3860 }
3861 })
3862 .collect::<Vec<_>>();
3863 if selection_ranges.is_empty() {
3864 return;
3865 }
3866
3867 let ranges = match columnar_state {
3868 ColumnarSelectionState::FromMouse { .. } => {
3869 let mut non_empty_ranges = selection_ranges
3870 .iter()
3871 .filter(|selection_range| selection_range.start != selection_range.end)
3872 .peekable();
3873 if non_empty_ranges.peek().is_some() {
3874 non_empty_ranges.cloned().collect()
3875 } else {
3876 selection_ranges
3877 }
3878 }
3879 _ => selection_ranges,
3880 };
3881
3882 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3883 s.select_ranges(ranges);
3884 });
3885 cx.notify();
3886 }
3887
3888 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3889 self.selections
3890 .all_adjusted(snapshot)
3891 .iter()
3892 .any(|selection| !selection.is_empty())
3893 }
3894
3895 pub fn has_pending_nonempty_selection(&self) -> bool {
3896 let pending_nonempty_selection = match self.selections.pending_anchor() {
3897 Some(Selection { start, end, .. }) => start != end,
3898 None => false,
3899 };
3900
3901 pending_nonempty_selection
3902 || (self.columnar_selection_state.is_some()
3903 && self.selections.disjoint_anchors().len() > 1)
3904 }
3905
3906 pub fn has_pending_selection(&self) -> bool {
3907 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3908 }
3909
3910 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3911 self.selection_mark_mode = false;
3912 self.selection_drag_state = SelectionDragState::None;
3913
3914 if self.clear_expanded_diff_hunks(cx) {
3915 cx.notify();
3916 return;
3917 }
3918 if self.dismiss_menus_and_popups(true, window, cx) {
3919 return;
3920 }
3921
3922 if self.mode.is_full()
3923 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3924 {
3925 return;
3926 }
3927
3928 cx.propagate();
3929 }
3930
3931 pub fn dismiss_menus_and_popups(
3932 &mut self,
3933 is_user_requested: bool,
3934 window: &mut Window,
3935 cx: &mut Context<Self>,
3936 ) -> bool {
3937 if self.take_rename(false, window, cx).is_some() {
3938 return true;
3939 }
3940
3941 if self.hide_blame_popover(true, cx) {
3942 return true;
3943 }
3944
3945 if hide_hover(self, cx) {
3946 return true;
3947 }
3948
3949 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3950 return true;
3951 }
3952
3953 if self.hide_context_menu(window, cx).is_some() {
3954 return true;
3955 }
3956
3957 if self.mouse_context_menu.take().is_some() {
3958 return true;
3959 }
3960
3961 if is_user_requested && self.discard_edit_prediction(true, cx) {
3962 return true;
3963 }
3964
3965 if self.snippet_stack.pop().is_some() {
3966 return true;
3967 }
3968
3969 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3970 self.dismiss_diagnostics(cx);
3971 return true;
3972 }
3973
3974 false
3975 }
3976
3977 fn linked_editing_ranges_for(
3978 &self,
3979 selection: Range<text::Anchor>,
3980 cx: &App,
3981 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3982 if self.linked_edit_ranges.is_empty() {
3983 return None;
3984 }
3985 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3986 selection.end.buffer_id.and_then(|end_buffer_id| {
3987 if selection.start.buffer_id != Some(end_buffer_id) {
3988 return None;
3989 }
3990 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3991 let snapshot = buffer.read(cx).snapshot();
3992 self.linked_edit_ranges
3993 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3994 .map(|ranges| (ranges, snapshot, buffer))
3995 })?;
3996 use text::ToOffset as TO;
3997 // find offset from the start of current range to current cursor position
3998 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3999
4000 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4001 let start_difference = start_offset - start_byte_offset;
4002 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4003 let end_difference = end_offset - start_byte_offset;
4004 // Current range has associated linked ranges.
4005 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4006 for range in linked_ranges.iter() {
4007 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4008 let end_offset = start_offset + end_difference;
4009 let start_offset = start_offset + start_difference;
4010 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4011 continue;
4012 }
4013 if self.selections.disjoint_anchor_ranges().any(|s| {
4014 if s.start.buffer_id != selection.start.buffer_id
4015 || s.end.buffer_id != selection.end.buffer_id
4016 {
4017 return false;
4018 }
4019 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4020 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4021 }) {
4022 continue;
4023 }
4024 let start = buffer_snapshot.anchor_after(start_offset);
4025 let end = buffer_snapshot.anchor_after(end_offset);
4026 linked_edits
4027 .entry(buffer.clone())
4028 .or_default()
4029 .push(start..end);
4030 }
4031 Some(linked_edits)
4032 }
4033
4034 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4035 let text: Arc<str> = text.into();
4036
4037 if self.read_only(cx) {
4038 return;
4039 }
4040
4041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4042
4043 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4044 let mut bracket_inserted = false;
4045 let mut edits = Vec::new();
4046 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4047 let mut new_selections = Vec::with_capacity(selections.len());
4048 let mut new_autoclose_regions = Vec::new();
4049 let snapshot = self.buffer.read(cx).read(cx);
4050 let mut clear_linked_edit_ranges = false;
4051
4052 for (selection, autoclose_region) in
4053 self.selections_with_autoclose_regions(selections, &snapshot)
4054 {
4055 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4056 // Determine if the inserted text matches the opening or closing
4057 // bracket of any of this language's bracket pairs.
4058 let mut bracket_pair = None;
4059 let mut is_bracket_pair_start = false;
4060 let mut is_bracket_pair_end = false;
4061 if !text.is_empty() {
4062 let mut bracket_pair_matching_end = None;
4063 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4064 // and they are removing the character that triggered IME popup.
4065 for (pair, enabled) in scope.brackets() {
4066 if !pair.close && !pair.surround {
4067 continue;
4068 }
4069
4070 if enabled && pair.start.ends_with(text.as_ref()) {
4071 let prefix_len = pair.start.len() - text.len();
4072 let preceding_text_matches_prefix = prefix_len == 0
4073 || (selection.start.column >= (prefix_len as u32)
4074 && snapshot.contains_str_at(
4075 Point::new(
4076 selection.start.row,
4077 selection.start.column - (prefix_len as u32),
4078 ),
4079 &pair.start[..prefix_len],
4080 ));
4081 if preceding_text_matches_prefix {
4082 bracket_pair = Some(pair.clone());
4083 is_bracket_pair_start = true;
4084 break;
4085 }
4086 }
4087 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4088 {
4089 // take first bracket pair matching end, but don't break in case a later bracket
4090 // pair matches start
4091 bracket_pair_matching_end = Some(pair.clone());
4092 }
4093 }
4094 if let Some(end) = bracket_pair_matching_end
4095 && bracket_pair.is_none()
4096 {
4097 bracket_pair = Some(end);
4098 is_bracket_pair_end = true;
4099 }
4100 }
4101
4102 if let Some(bracket_pair) = bracket_pair {
4103 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4104 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4105 let auto_surround =
4106 self.use_auto_surround && snapshot_settings.use_auto_surround;
4107 if selection.is_empty() {
4108 if is_bracket_pair_start {
4109 // If the inserted text is a suffix of an opening bracket and the
4110 // selection is preceded by the rest of the opening bracket, then
4111 // insert the closing bracket.
4112 let following_text_allows_autoclose = snapshot
4113 .chars_at(selection.start)
4114 .next()
4115 .is_none_or(|c| scope.should_autoclose_before(c));
4116
4117 let preceding_text_allows_autoclose = selection.start.column == 0
4118 || snapshot
4119 .reversed_chars_at(selection.start)
4120 .next()
4121 .is_none_or(|c| {
4122 bracket_pair.start != bracket_pair.end
4123 || !snapshot
4124 .char_classifier_at(selection.start)
4125 .is_word(c)
4126 });
4127
4128 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4129 && bracket_pair.start.len() == 1
4130 {
4131 let target = bracket_pair.start.chars().next().unwrap();
4132 let current_line_count = snapshot
4133 .reversed_chars_at(selection.start)
4134 .take_while(|&c| c != '\n')
4135 .filter(|&c| c == target)
4136 .count();
4137 current_line_count % 2 == 1
4138 } else {
4139 false
4140 };
4141
4142 if autoclose
4143 && bracket_pair.close
4144 && following_text_allows_autoclose
4145 && preceding_text_allows_autoclose
4146 && !is_closing_quote
4147 {
4148 let anchor = snapshot.anchor_before(selection.end);
4149 new_selections.push((selection.map(|_| anchor), text.len()));
4150 new_autoclose_regions.push((
4151 anchor,
4152 text.len(),
4153 selection.id,
4154 bracket_pair.clone(),
4155 ));
4156 edits.push((
4157 selection.range(),
4158 format!("{}{}", text, bracket_pair.end).into(),
4159 ));
4160 bracket_inserted = true;
4161 continue;
4162 }
4163 }
4164
4165 if let Some(region) = autoclose_region {
4166 // If the selection is followed by an auto-inserted closing bracket,
4167 // then don't insert that closing bracket again; just move the selection
4168 // past the closing bracket.
4169 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4170 && text.as_ref() == region.pair.end.as_str()
4171 && snapshot.contains_str_at(region.range.end, text.as_ref());
4172 if should_skip {
4173 let anchor = snapshot.anchor_after(selection.end);
4174 new_selections
4175 .push((selection.map(|_| anchor), region.pair.end.len()));
4176 continue;
4177 }
4178 }
4179
4180 let always_treat_brackets_as_autoclosed = snapshot
4181 .language_settings_at(selection.start, cx)
4182 .always_treat_brackets_as_autoclosed;
4183 if always_treat_brackets_as_autoclosed
4184 && is_bracket_pair_end
4185 && snapshot.contains_str_at(selection.end, text.as_ref())
4186 {
4187 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4188 // and the inserted text is a closing bracket and the selection is followed
4189 // by the closing bracket then move the selection past the closing bracket.
4190 let anchor = snapshot.anchor_after(selection.end);
4191 new_selections.push((selection.map(|_| anchor), text.len()));
4192 continue;
4193 }
4194 }
4195 // If an opening bracket is 1 character long and is typed while
4196 // text is selected, then surround that text with the bracket pair.
4197 else if auto_surround
4198 && bracket_pair.surround
4199 && is_bracket_pair_start
4200 && bracket_pair.start.chars().count() == 1
4201 {
4202 edits.push((selection.start..selection.start, text.clone()));
4203 edits.push((
4204 selection.end..selection.end,
4205 bracket_pair.end.as_str().into(),
4206 ));
4207 bracket_inserted = true;
4208 new_selections.push((
4209 Selection {
4210 id: selection.id,
4211 start: snapshot.anchor_after(selection.start),
4212 end: snapshot.anchor_before(selection.end),
4213 reversed: selection.reversed,
4214 goal: selection.goal,
4215 },
4216 0,
4217 ));
4218 continue;
4219 }
4220 }
4221 }
4222
4223 if self.auto_replace_emoji_shortcode
4224 && selection.is_empty()
4225 && text.as_ref().ends_with(':')
4226 && let Some(possible_emoji_short_code) =
4227 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4228 && !possible_emoji_short_code.is_empty()
4229 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4230 {
4231 let emoji_shortcode_start = Point::new(
4232 selection.start.row,
4233 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4234 );
4235
4236 // Remove shortcode from buffer
4237 edits.push((
4238 emoji_shortcode_start..selection.start,
4239 "".to_string().into(),
4240 ));
4241 new_selections.push((
4242 Selection {
4243 id: selection.id,
4244 start: snapshot.anchor_after(emoji_shortcode_start),
4245 end: snapshot.anchor_before(selection.start),
4246 reversed: selection.reversed,
4247 goal: selection.goal,
4248 },
4249 0,
4250 ));
4251
4252 // Insert emoji
4253 let selection_start_anchor = snapshot.anchor_after(selection.start);
4254 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4255 edits.push((selection.start..selection.end, emoji.to_string().into()));
4256
4257 continue;
4258 }
4259
4260 // If not handling any auto-close operation, then just replace the selected
4261 // text with the given input and move the selection to the end of the
4262 // newly inserted text.
4263 let anchor = snapshot.anchor_after(selection.end);
4264 if !self.linked_edit_ranges.is_empty() {
4265 let start_anchor = snapshot.anchor_before(selection.start);
4266
4267 let is_word_char = text.chars().next().is_none_or(|char| {
4268 let classifier = snapshot
4269 .char_classifier_at(start_anchor.to_offset(&snapshot))
4270 .scope_context(Some(CharScopeContext::LinkedEdit));
4271 classifier.is_word(char)
4272 });
4273
4274 if is_word_char {
4275 if let Some(ranges) = self
4276 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4277 {
4278 for (buffer, edits) in ranges {
4279 linked_edits
4280 .entry(buffer.clone())
4281 .or_default()
4282 .extend(edits.into_iter().map(|range| (range, text.clone())));
4283 }
4284 }
4285 } else {
4286 clear_linked_edit_ranges = true;
4287 }
4288 }
4289
4290 new_selections.push((selection.map(|_| anchor), 0));
4291 edits.push((selection.start..selection.end, text.clone()));
4292 }
4293
4294 drop(snapshot);
4295
4296 self.transact(window, cx, |this, window, cx| {
4297 if clear_linked_edit_ranges {
4298 this.linked_edit_ranges.clear();
4299 }
4300 let initial_buffer_versions =
4301 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4302
4303 this.buffer.update(cx, |buffer, cx| {
4304 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4305 });
4306 for (buffer, edits) in linked_edits {
4307 buffer.update(cx, |buffer, cx| {
4308 let snapshot = buffer.snapshot();
4309 let edits = edits
4310 .into_iter()
4311 .map(|(range, text)| {
4312 use text::ToPoint as TP;
4313 let end_point = TP::to_point(&range.end, &snapshot);
4314 let start_point = TP::to_point(&range.start, &snapshot);
4315 (start_point..end_point, text)
4316 })
4317 .sorted_by_key(|(range, _)| range.start);
4318 buffer.edit(edits, None, cx);
4319 })
4320 }
4321 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4322 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4323 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4324 let new_selections =
4325 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4326 .zip(new_selection_deltas)
4327 .map(|(selection, delta)| Selection {
4328 id: selection.id,
4329 start: selection.start + delta,
4330 end: selection.end + delta,
4331 reversed: selection.reversed,
4332 goal: SelectionGoal::None,
4333 })
4334 .collect::<Vec<_>>();
4335
4336 let mut i = 0;
4337 for (position, delta, selection_id, pair) in new_autoclose_regions {
4338 let position = position.to_offset(map.buffer_snapshot()) + delta;
4339 let start = map.buffer_snapshot().anchor_before(position);
4340 let end = map.buffer_snapshot().anchor_after(position);
4341 while let Some(existing_state) = this.autoclose_regions.get(i) {
4342 match existing_state
4343 .range
4344 .start
4345 .cmp(&start, map.buffer_snapshot())
4346 {
4347 Ordering::Less => i += 1,
4348 Ordering::Greater => break,
4349 Ordering::Equal => {
4350 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4351 Ordering::Less => i += 1,
4352 Ordering::Equal => break,
4353 Ordering::Greater => break,
4354 }
4355 }
4356 }
4357 }
4358 this.autoclose_regions.insert(
4359 i,
4360 AutocloseRegion {
4361 selection_id,
4362 range: start..end,
4363 pair,
4364 },
4365 );
4366 }
4367
4368 let had_active_edit_prediction = this.has_active_edit_prediction();
4369 this.change_selections(
4370 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4371 window,
4372 cx,
4373 |s| s.select(new_selections),
4374 );
4375
4376 if !bracket_inserted
4377 && let Some(on_type_format_task) =
4378 this.trigger_on_type_formatting(text.to_string(), window, cx)
4379 {
4380 on_type_format_task.detach_and_log_err(cx);
4381 }
4382
4383 let editor_settings = EditorSettings::get_global(cx);
4384 if bracket_inserted
4385 && (editor_settings.auto_signature_help
4386 || editor_settings.show_signature_help_after_edits)
4387 {
4388 this.show_signature_help(&ShowSignatureHelp, window, cx);
4389 }
4390
4391 let trigger_in_words =
4392 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4393 if this.hard_wrap.is_some() {
4394 let latest: Range<Point> = this.selections.newest(&map).range();
4395 if latest.is_empty()
4396 && this
4397 .buffer()
4398 .read(cx)
4399 .snapshot(cx)
4400 .line_len(MultiBufferRow(latest.start.row))
4401 == latest.start.column
4402 {
4403 this.rewrap_impl(
4404 RewrapOptions {
4405 override_language_settings: true,
4406 preserve_existing_whitespace: true,
4407 },
4408 cx,
4409 )
4410 }
4411 }
4412 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4413 refresh_linked_ranges(this, window, cx);
4414 this.refresh_edit_prediction(true, false, window, cx);
4415 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4416 });
4417 }
4418
4419 fn find_possible_emoji_shortcode_at_position(
4420 snapshot: &MultiBufferSnapshot,
4421 position: Point,
4422 ) -> Option<String> {
4423 let mut chars = Vec::new();
4424 let mut found_colon = false;
4425 for char in snapshot.reversed_chars_at(position).take(100) {
4426 // Found a possible emoji shortcode in the middle of the buffer
4427 if found_colon {
4428 if char.is_whitespace() {
4429 chars.reverse();
4430 return Some(chars.iter().collect());
4431 }
4432 // If the previous character is not a whitespace, we are in the middle of a word
4433 // and we only want to complete the shortcode if the word is made up of other emojis
4434 let mut containing_word = String::new();
4435 for ch in snapshot
4436 .reversed_chars_at(position)
4437 .skip(chars.len() + 1)
4438 .take(100)
4439 {
4440 if ch.is_whitespace() {
4441 break;
4442 }
4443 containing_word.push(ch);
4444 }
4445 let containing_word = containing_word.chars().rev().collect::<String>();
4446 if util::word_consists_of_emojis(containing_word.as_str()) {
4447 chars.reverse();
4448 return Some(chars.iter().collect());
4449 }
4450 }
4451
4452 if char.is_whitespace() || !char.is_ascii() {
4453 return None;
4454 }
4455 if char == ':' {
4456 found_colon = true;
4457 } else {
4458 chars.push(char);
4459 }
4460 }
4461 // Found a possible emoji shortcode at the beginning of the buffer
4462 chars.reverse();
4463 Some(chars.iter().collect())
4464 }
4465
4466 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4467 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4468 self.transact(window, cx, |this, window, cx| {
4469 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4470 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4471 let multi_buffer = this.buffer.read(cx);
4472 let buffer = multi_buffer.snapshot(cx);
4473 selections
4474 .iter()
4475 .map(|selection| {
4476 let start_point = selection.start.to_point(&buffer);
4477 let mut existing_indent =
4478 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4479 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4480 let start = selection.start;
4481 let end = selection.end;
4482 let selection_is_empty = start == end;
4483 let language_scope = buffer.language_scope_at(start);
4484 let (
4485 comment_delimiter,
4486 doc_delimiter,
4487 insert_extra_newline,
4488 indent_on_newline,
4489 indent_on_extra_newline,
4490 ) = if let Some(language) = &language_scope {
4491 let mut insert_extra_newline =
4492 insert_extra_newline_brackets(&buffer, start..end, language)
4493 || insert_extra_newline_tree_sitter(&buffer, start..end);
4494
4495 // Comment extension on newline is allowed only for cursor selections
4496 let comment_delimiter = maybe!({
4497 if !selection_is_empty {
4498 return None;
4499 }
4500
4501 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4502 return None;
4503 }
4504
4505 let delimiters = language.line_comment_prefixes();
4506 let max_len_of_delimiter =
4507 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4508 let (snapshot, range) =
4509 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4510
4511 let num_of_whitespaces = snapshot
4512 .chars_for_range(range.clone())
4513 .take_while(|c| c.is_whitespace())
4514 .count();
4515 let comment_candidate = snapshot
4516 .chars_for_range(range.clone())
4517 .skip(num_of_whitespaces)
4518 .take(max_len_of_delimiter)
4519 .collect::<String>();
4520 let (delimiter, trimmed_len) = delimiters
4521 .iter()
4522 .filter_map(|delimiter| {
4523 let prefix = delimiter.trim_end();
4524 if comment_candidate.starts_with(prefix) {
4525 Some((delimiter, prefix.len()))
4526 } else {
4527 None
4528 }
4529 })
4530 .max_by_key(|(_, len)| *len)?;
4531
4532 if let Some(BlockCommentConfig {
4533 start: block_start, ..
4534 }) = language.block_comment()
4535 {
4536 let block_start_trimmed = block_start.trim_end();
4537 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4538 let line_content = snapshot
4539 .chars_for_range(range)
4540 .skip(num_of_whitespaces)
4541 .take(block_start_trimmed.len())
4542 .collect::<String>();
4543
4544 if line_content.starts_with(block_start_trimmed) {
4545 return None;
4546 }
4547 }
4548 }
4549
4550 let cursor_is_placed_after_comment_marker =
4551 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4552 if cursor_is_placed_after_comment_marker {
4553 Some(delimiter.clone())
4554 } else {
4555 None
4556 }
4557 });
4558
4559 let mut indent_on_newline = IndentSize::spaces(0);
4560 let mut indent_on_extra_newline = IndentSize::spaces(0);
4561
4562 let doc_delimiter = maybe!({
4563 if !selection_is_empty {
4564 return None;
4565 }
4566
4567 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4568 return None;
4569 }
4570
4571 let BlockCommentConfig {
4572 start: start_tag,
4573 end: end_tag,
4574 prefix: delimiter,
4575 tab_size: len,
4576 } = language.documentation_comment()?;
4577 let is_within_block_comment = buffer
4578 .language_scope_at(start_point)
4579 .is_some_and(|scope| scope.override_name() == Some("comment"));
4580 if !is_within_block_comment {
4581 return None;
4582 }
4583
4584 let (snapshot, range) =
4585 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4586
4587 let num_of_whitespaces = snapshot
4588 .chars_for_range(range.clone())
4589 .take_while(|c| c.is_whitespace())
4590 .count();
4591
4592 // 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.
4593 let column = start_point.column;
4594 let cursor_is_after_start_tag = {
4595 let start_tag_len = start_tag.len();
4596 let start_tag_line = snapshot
4597 .chars_for_range(range.clone())
4598 .skip(num_of_whitespaces)
4599 .take(start_tag_len)
4600 .collect::<String>();
4601 if start_tag_line.starts_with(start_tag.as_ref()) {
4602 num_of_whitespaces + start_tag_len <= column as usize
4603 } else {
4604 false
4605 }
4606 };
4607
4608 let cursor_is_after_delimiter = {
4609 let delimiter_trim = delimiter.trim_end();
4610 let delimiter_line = snapshot
4611 .chars_for_range(range.clone())
4612 .skip(num_of_whitespaces)
4613 .take(delimiter_trim.len())
4614 .collect::<String>();
4615 if delimiter_line.starts_with(delimiter_trim) {
4616 num_of_whitespaces + delimiter_trim.len() <= column as usize
4617 } else {
4618 false
4619 }
4620 };
4621
4622 let cursor_is_before_end_tag_if_exists = {
4623 let mut char_position = 0u32;
4624 let mut end_tag_offset = None;
4625
4626 'outer: for chunk in snapshot.text_for_range(range) {
4627 if let Some(byte_pos) = chunk.find(&**end_tag) {
4628 let chars_before_match =
4629 chunk[..byte_pos].chars().count() as u32;
4630 end_tag_offset =
4631 Some(char_position + chars_before_match);
4632 break 'outer;
4633 }
4634 char_position += chunk.chars().count() as u32;
4635 }
4636
4637 if let Some(end_tag_offset) = end_tag_offset {
4638 let cursor_is_before_end_tag = column <= end_tag_offset;
4639 if cursor_is_after_start_tag {
4640 if cursor_is_before_end_tag {
4641 insert_extra_newline = true;
4642 }
4643 let cursor_is_at_start_of_end_tag =
4644 column == end_tag_offset;
4645 if cursor_is_at_start_of_end_tag {
4646 indent_on_extra_newline.len = *len;
4647 }
4648 }
4649 cursor_is_before_end_tag
4650 } else {
4651 true
4652 }
4653 };
4654
4655 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4656 && cursor_is_before_end_tag_if_exists
4657 {
4658 if cursor_is_after_start_tag {
4659 indent_on_newline.len = *len;
4660 }
4661 Some(delimiter.clone())
4662 } else {
4663 None
4664 }
4665 });
4666
4667 (
4668 comment_delimiter,
4669 doc_delimiter,
4670 insert_extra_newline,
4671 indent_on_newline,
4672 indent_on_extra_newline,
4673 )
4674 } else {
4675 (
4676 None,
4677 None,
4678 false,
4679 IndentSize::default(),
4680 IndentSize::default(),
4681 )
4682 };
4683
4684 let prevent_auto_indent = doc_delimiter.is_some();
4685 let delimiter = comment_delimiter.or(doc_delimiter);
4686
4687 let capacity_for_delimiter =
4688 delimiter.as_deref().map(str::len).unwrap_or_default();
4689 let mut new_text = String::with_capacity(
4690 1 + capacity_for_delimiter
4691 + existing_indent.len as usize
4692 + indent_on_newline.len as usize
4693 + indent_on_extra_newline.len as usize,
4694 );
4695 new_text.push('\n');
4696 new_text.extend(existing_indent.chars());
4697 new_text.extend(indent_on_newline.chars());
4698
4699 if let Some(delimiter) = &delimiter {
4700 new_text.push_str(delimiter);
4701 }
4702
4703 if insert_extra_newline {
4704 new_text.push('\n');
4705 new_text.extend(existing_indent.chars());
4706 new_text.extend(indent_on_extra_newline.chars());
4707 }
4708
4709 let anchor = buffer.anchor_after(end);
4710 let new_selection = selection.map(|_| anchor);
4711 (
4712 ((start..end, new_text), prevent_auto_indent),
4713 (insert_extra_newline, new_selection),
4714 )
4715 })
4716 .unzip()
4717 };
4718
4719 let mut auto_indent_edits = Vec::new();
4720 let mut edits = Vec::new();
4721 for (edit, prevent_auto_indent) in edits_with_flags {
4722 if prevent_auto_indent {
4723 edits.push(edit);
4724 } else {
4725 auto_indent_edits.push(edit);
4726 }
4727 }
4728 if !edits.is_empty() {
4729 this.edit(edits, cx);
4730 }
4731 if !auto_indent_edits.is_empty() {
4732 this.edit_with_autoindent(auto_indent_edits, cx);
4733 }
4734
4735 let buffer = this.buffer.read(cx).snapshot(cx);
4736 let new_selections = selection_info
4737 .into_iter()
4738 .map(|(extra_newline_inserted, new_selection)| {
4739 let mut cursor = new_selection.end.to_point(&buffer);
4740 if extra_newline_inserted {
4741 cursor.row -= 1;
4742 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4743 }
4744 new_selection.map(|_| cursor)
4745 })
4746 .collect();
4747
4748 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4749 this.refresh_edit_prediction(true, false, window, cx);
4750 });
4751 }
4752
4753 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4755
4756 let buffer = self.buffer.read(cx);
4757 let snapshot = buffer.snapshot(cx);
4758
4759 let mut edits = Vec::new();
4760 let mut rows = Vec::new();
4761
4762 for (rows_inserted, selection) in self
4763 .selections
4764 .all_adjusted(&self.display_snapshot(cx))
4765 .into_iter()
4766 .enumerate()
4767 {
4768 let cursor = selection.head();
4769 let row = cursor.row;
4770
4771 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4772
4773 let newline = "\n".to_string();
4774 edits.push((start_of_line..start_of_line, newline));
4775
4776 rows.push(row + rows_inserted as u32);
4777 }
4778
4779 self.transact(window, cx, |editor, window, cx| {
4780 editor.edit(edits, cx);
4781
4782 editor.change_selections(Default::default(), window, cx, |s| {
4783 let mut index = 0;
4784 s.move_cursors_with(|map, _, _| {
4785 let row = rows[index];
4786 index += 1;
4787
4788 let point = Point::new(row, 0);
4789 let boundary = map.next_line_boundary(point).1;
4790 let clipped = map.clip_point(boundary, Bias::Left);
4791
4792 (clipped, SelectionGoal::None)
4793 });
4794 });
4795
4796 let mut indent_edits = Vec::new();
4797 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4798 for row in rows {
4799 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4800 for (row, indent) in indents {
4801 if indent.len == 0 {
4802 continue;
4803 }
4804
4805 let text = match indent.kind {
4806 IndentKind::Space => " ".repeat(indent.len as usize),
4807 IndentKind::Tab => "\t".repeat(indent.len as usize),
4808 };
4809 let point = Point::new(row.0, 0);
4810 indent_edits.push((point..point, text));
4811 }
4812 }
4813 editor.edit(indent_edits, cx);
4814 });
4815 }
4816
4817 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4819
4820 let buffer = self.buffer.read(cx);
4821 let snapshot = buffer.snapshot(cx);
4822
4823 let mut edits = Vec::new();
4824 let mut rows = Vec::new();
4825 let mut rows_inserted = 0;
4826
4827 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4828 let cursor = selection.head();
4829 let row = cursor.row;
4830
4831 let point = Point::new(row + 1, 0);
4832 let start_of_line = snapshot.clip_point(point, Bias::Left);
4833
4834 let newline = "\n".to_string();
4835 edits.push((start_of_line..start_of_line, newline));
4836
4837 rows_inserted += 1;
4838 rows.push(row + rows_inserted);
4839 }
4840
4841 self.transact(window, cx, |editor, window, cx| {
4842 editor.edit(edits, cx);
4843
4844 editor.change_selections(Default::default(), window, cx, |s| {
4845 let mut index = 0;
4846 s.move_cursors_with(|map, _, _| {
4847 let row = rows[index];
4848 index += 1;
4849
4850 let point = Point::new(row, 0);
4851 let boundary = map.next_line_boundary(point).1;
4852 let clipped = map.clip_point(boundary, Bias::Left);
4853
4854 (clipped, SelectionGoal::None)
4855 });
4856 });
4857
4858 let mut indent_edits = Vec::new();
4859 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4860 for row in rows {
4861 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4862 for (row, indent) in indents {
4863 if indent.len == 0 {
4864 continue;
4865 }
4866
4867 let text = match indent.kind {
4868 IndentKind::Space => " ".repeat(indent.len as usize),
4869 IndentKind::Tab => "\t".repeat(indent.len as usize),
4870 };
4871 let point = Point::new(row.0, 0);
4872 indent_edits.push((point..point, text));
4873 }
4874 }
4875 editor.edit(indent_edits, cx);
4876 });
4877 }
4878
4879 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4880 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4881 original_indent_columns: Vec::new(),
4882 });
4883 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4884 }
4885
4886 fn insert_with_autoindent_mode(
4887 &mut self,
4888 text: &str,
4889 autoindent_mode: Option<AutoindentMode>,
4890 window: &mut Window,
4891 cx: &mut Context<Self>,
4892 ) {
4893 if self.read_only(cx) {
4894 return;
4895 }
4896
4897 let text: Arc<str> = text.into();
4898 self.transact(window, cx, |this, window, cx| {
4899 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4900 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4901 let anchors = {
4902 let snapshot = buffer.read(cx);
4903 old_selections
4904 .iter()
4905 .map(|s| {
4906 let anchor = snapshot.anchor_after(s.head());
4907 s.map(|_| anchor)
4908 })
4909 .collect::<Vec<_>>()
4910 };
4911 buffer.edit(
4912 old_selections
4913 .iter()
4914 .map(|s| (s.start..s.end, text.clone())),
4915 autoindent_mode,
4916 cx,
4917 );
4918 anchors
4919 });
4920
4921 this.change_selections(Default::default(), window, cx, |s| {
4922 s.select_anchors(selection_anchors);
4923 });
4924
4925 cx.notify();
4926 });
4927 }
4928
4929 fn trigger_completion_on_input(
4930 &mut self,
4931 text: &str,
4932 trigger_in_words: bool,
4933 window: &mut Window,
4934 cx: &mut Context<Self>,
4935 ) {
4936 let completions_source = self
4937 .context_menu
4938 .borrow()
4939 .as_ref()
4940 .and_then(|menu| match menu {
4941 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4942 CodeContextMenu::CodeActions(_) => None,
4943 });
4944
4945 match completions_source {
4946 Some(CompletionsMenuSource::Words { .. }) => {
4947 self.open_or_update_completions_menu(
4948 Some(CompletionsMenuSource::Words {
4949 ignore_threshold: false,
4950 }),
4951 None,
4952 window,
4953 cx,
4954 );
4955 }
4956 Some(CompletionsMenuSource::Normal)
4957 | Some(CompletionsMenuSource::SnippetChoices)
4958 | None
4959 if self.is_completion_trigger(
4960 text,
4961 trigger_in_words,
4962 completions_source.is_some(),
4963 cx,
4964 ) =>
4965 {
4966 self.show_completions(
4967 &ShowCompletions {
4968 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4969 },
4970 window,
4971 cx,
4972 )
4973 }
4974 _ => {
4975 self.hide_context_menu(window, cx);
4976 }
4977 }
4978 }
4979
4980 fn is_completion_trigger(
4981 &self,
4982 text: &str,
4983 trigger_in_words: bool,
4984 menu_is_open: bool,
4985 cx: &mut Context<Self>,
4986 ) -> bool {
4987 let position = self.selections.newest_anchor().head();
4988 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4989 return false;
4990 };
4991
4992 if let Some(completion_provider) = &self.completion_provider {
4993 completion_provider.is_completion_trigger(
4994 &buffer,
4995 position.text_anchor,
4996 text,
4997 trigger_in_words,
4998 menu_is_open,
4999 cx,
5000 )
5001 } else {
5002 false
5003 }
5004 }
5005
5006 /// If any empty selections is touching the start of its innermost containing autoclose
5007 /// region, expand it to select the brackets.
5008 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5009 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5010 let buffer = self.buffer.read(cx).read(cx);
5011 let new_selections = self
5012 .selections_with_autoclose_regions(selections, &buffer)
5013 .map(|(mut selection, region)| {
5014 if !selection.is_empty() {
5015 return selection;
5016 }
5017
5018 if let Some(region) = region {
5019 let mut range = region.range.to_offset(&buffer);
5020 if selection.start == range.start && range.start >= region.pair.start.len() {
5021 range.start -= region.pair.start.len();
5022 if buffer.contains_str_at(range.start, ®ion.pair.start)
5023 && buffer.contains_str_at(range.end, ®ion.pair.end)
5024 {
5025 range.end += region.pair.end.len();
5026 selection.start = range.start;
5027 selection.end = range.end;
5028
5029 return selection;
5030 }
5031 }
5032 }
5033
5034 let always_treat_brackets_as_autoclosed = buffer
5035 .language_settings_at(selection.start, cx)
5036 .always_treat_brackets_as_autoclosed;
5037
5038 if !always_treat_brackets_as_autoclosed {
5039 return selection;
5040 }
5041
5042 if let Some(scope) = buffer.language_scope_at(selection.start) {
5043 for (pair, enabled) in scope.brackets() {
5044 if !enabled || !pair.close {
5045 continue;
5046 }
5047
5048 if buffer.contains_str_at(selection.start, &pair.end) {
5049 let pair_start_len = pair.start.len();
5050 if buffer.contains_str_at(
5051 selection.start.saturating_sub(pair_start_len),
5052 &pair.start,
5053 ) {
5054 selection.start -= pair_start_len;
5055 selection.end += pair.end.len();
5056
5057 return selection;
5058 }
5059 }
5060 }
5061 }
5062
5063 selection
5064 })
5065 .collect();
5066
5067 drop(buffer);
5068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5069 selections.select(new_selections)
5070 });
5071 }
5072
5073 /// Iterate the given selections, and for each one, find the smallest surrounding
5074 /// autoclose region. This uses the ordering of the selections and the autoclose
5075 /// regions to avoid repeated comparisons.
5076 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5077 &'a self,
5078 selections: impl IntoIterator<Item = Selection<D>>,
5079 buffer: &'a MultiBufferSnapshot,
5080 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5081 let mut i = 0;
5082 let mut regions = self.autoclose_regions.as_slice();
5083 selections.into_iter().map(move |selection| {
5084 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5085
5086 let mut enclosing = None;
5087 while let Some(pair_state) = regions.get(i) {
5088 if pair_state.range.end.to_offset(buffer) < range.start {
5089 regions = ®ions[i + 1..];
5090 i = 0;
5091 } else if pair_state.range.start.to_offset(buffer) > range.end {
5092 break;
5093 } else {
5094 if pair_state.selection_id == selection.id {
5095 enclosing = Some(pair_state);
5096 }
5097 i += 1;
5098 }
5099 }
5100
5101 (selection, enclosing)
5102 })
5103 }
5104
5105 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5106 fn invalidate_autoclose_regions(
5107 &mut self,
5108 mut selections: &[Selection<Anchor>],
5109 buffer: &MultiBufferSnapshot,
5110 ) {
5111 self.autoclose_regions.retain(|state| {
5112 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5113 return false;
5114 }
5115
5116 let mut i = 0;
5117 while let Some(selection) = selections.get(i) {
5118 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5119 selections = &selections[1..];
5120 continue;
5121 }
5122 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5123 break;
5124 }
5125 if selection.id == state.selection_id {
5126 return true;
5127 } else {
5128 i += 1;
5129 }
5130 }
5131 false
5132 });
5133 }
5134
5135 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5136 let offset = position.to_offset(buffer);
5137 let (word_range, kind) =
5138 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5139 if offset > word_range.start && kind == Some(CharKind::Word) {
5140 Some(
5141 buffer
5142 .text_for_range(word_range.start..offset)
5143 .collect::<String>(),
5144 )
5145 } else {
5146 None
5147 }
5148 }
5149
5150 pub fn visible_excerpts(
5151 &self,
5152 cx: &mut Context<Editor>,
5153 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5154 let Some(project) = self.project() else {
5155 return HashMap::default();
5156 };
5157 let project = project.read(cx);
5158 let multi_buffer = self.buffer().read(cx);
5159 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5160 let multi_buffer_visible_start = self
5161 .scroll_manager
5162 .anchor()
5163 .anchor
5164 .to_point(&multi_buffer_snapshot);
5165 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5166 multi_buffer_visible_start
5167 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5168 Bias::Left,
5169 );
5170 multi_buffer_snapshot
5171 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5172 .into_iter()
5173 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5174 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5175 let buffer_file = project::File::from_dyn(buffer.file())?;
5176 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5177 let worktree_entry = buffer_worktree
5178 .read(cx)
5179 .entry_for_id(buffer_file.project_entry_id()?)?;
5180 if worktree_entry.is_ignored {
5181 None
5182 } else {
5183 Some((
5184 excerpt_id,
5185 (
5186 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5187 buffer.version().clone(),
5188 excerpt_visible_range,
5189 ),
5190 ))
5191 }
5192 })
5193 .collect()
5194 }
5195
5196 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5197 TextLayoutDetails {
5198 text_system: window.text_system().clone(),
5199 editor_style: self.style.clone().unwrap(),
5200 rem_size: window.rem_size(),
5201 scroll_anchor: self.scroll_manager.anchor(),
5202 visible_rows: self.visible_line_count(),
5203 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5204 }
5205 }
5206
5207 fn trigger_on_type_formatting(
5208 &self,
5209 input: String,
5210 window: &mut Window,
5211 cx: &mut Context<Self>,
5212 ) -> Option<Task<Result<()>>> {
5213 if input.len() != 1 {
5214 return None;
5215 }
5216
5217 let project = self.project()?;
5218 let position = self.selections.newest_anchor().head();
5219 let (buffer, buffer_position) = self
5220 .buffer
5221 .read(cx)
5222 .text_anchor_for_position(position, cx)?;
5223
5224 let settings = language_settings::language_settings(
5225 buffer
5226 .read(cx)
5227 .language_at(buffer_position)
5228 .map(|l| l.name()),
5229 buffer.read(cx).file(),
5230 cx,
5231 );
5232 if !settings.use_on_type_format {
5233 return None;
5234 }
5235
5236 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5237 // hence we do LSP request & edit on host side only — add formats to host's history.
5238 let push_to_lsp_host_history = true;
5239 // If this is not the host, append its history with new edits.
5240 let push_to_client_history = project.read(cx).is_via_collab();
5241
5242 let on_type_formatting = project.update(cx, |project, cx| {
5243 project.on_type_format(
5244 buffer.clone(),
5245 buffer_position,
5246 input,
5247 push_to_lsp_host_history,
5248 cx,
5249 )
5250 });
5251 Some(cx.spawn_in(window, async move |editor, cx| {
5252 if let Some(transaction) = on_type_formatting.await? {
5253 if push_to_client_history {
5254 buffer
5255 .update(cx, |buffer, _| {
5256 buffer.push_transaction(transaction, Instant::now());
5257 buffer.finalize_last_transaction();
5258 })
5259 .ok();
5260 }
5261 editor.update(cx, |editor, cx| {
5262 editor.refresh_document_highlights(cx);
5263 })?;
5264 }
5265 Ok(())
5266 }))
5267 }
5268
5269 pub fn show_word_completions(
5270 &mut self,
5271 _: &ShowWordCompletions,
5272 window: &mut Window,
5273 cx: &mut Context<Self>,
5274 ) {
5275 self.open_or_update_completions_menu(
5276 Some(CompletionsMenuSource::Words {
5277 ignore_threshold: true,
5278 }),
5279 None,
5280 window,
5281 cx,
5282 );
5283 }
5284
5285 pub fn show_completions(
5286 &mut self,
5287 options: &ShowCompletions,
5288 window: &mut Window,
5289 cx: &mut Context<Self>,
5290 ) {
5291 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5292 }
5293
5294 fn open_or_update_completions_menu(
5295 &mut self,
5296 requested_source: Option<CompletionsMenuSource>,
5297 trigger: Option<&str>,
5298 window: &mut Window,
5299 cx: &mut Context<Self>,
5300 ) {
5301 if self.pending_rename.is_some() {
5302 return;
5303 }
5304
5305 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5306
5307 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5308 // inserted and selected. To handle that case, the start of the selection is used so that
5309 // the menu starts with all choices.
5310 let position = self
5311 .selections
5312 .newest_anchor()
5313 .start
5314 .bias_right(&multibuffer_snapshot);
5315 if position.diff_base_anchor.is_some() {
5316 return;
5317 }
5318 let buffer_position = multibuffer_snapshot.anchor_before(position);
5319 let Some(buffer) = buffer_position
5320 .buffer_id
5321 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5322 else {
5323 return;
5324 };
5325 let buffer_snapshot = buffer.read(cx).snapshot();
5326
5327 let query: Option<Arc<String>> =
5328 Self::completion_query(&multibuffer_snapshot, buffer_position)
5329 .map(|query| query.into());
5330
5331 drop(multibuffer_snapshot);
5332
5333 // Hide the current completions menu when query is empty. Without this, cached
5334 // completions from before the trigger char may be reused (#32774).
5335 if query.is_none() {
5336 let menu_is_open = matches!(
5337 self.context_menu.borrow().as_ref(),
5338 Some(CodeContextMenu::Completions(_))
5339 );
5340 if menu_is_open {
5341 self.hide_context_menu(window, cx);
5342 }
5343 }
5344
5345 let mut ignore_word_threshold = false;
5346 let provider = match requested_source {
5347 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5348 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5349 ignore_word_threshold = ignore_threshold;
5350 None
5351 }
5352 Some(CompletionsMenuSource::SnippetChoices) => {
5353 log::error!("bug: SnippetChoices requested_source is not handled");
5354 None
5355 }
5356 };
5357
5358 let sort_completions = provider
5359 .as_ref()
5360 .is_some_and(|provider| provider.sort_completions());
5361
5362 let filter_completions = provider
5363 .as_ref()
5364 .is_none_or(|provider| provider.filter_completions());
5365
5366 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5367 if filter_completions {
5368 menu.filter(query.clone(), provider.clone(), window, cx);
5369 }
5370 // When `is_incomplete` is false, no need to re-query completions when the current query
5371 // is a suffix of the initial query.
5372 if !menu.is_incomplete {
5373 // If the new query is a suffix of the old query (typing more characters) and
5374 // the previous result was complete, the existing completions can be filtered.
5375 //
5376 // Note that this is always true for snippet completions.
5377 let query_matches = match (&menu.initial_query, &query) {
5378 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5379 (None, _) => true,
5380 _ => false,
5381 };
5382 if query_matches {
5383 let position_matches = if menu.initial_position == position {
5384 true
5385 } else {
5386 let snapshot = self.buffer.read(cx).read(cx);
5387 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5388 };
5389 if position_matches {
5390 return;
5391 }
5392 }
5393 }
5394 };
5395
5396 let trigger_kind = match trigger {
5397 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5398 CompletionTriggerKind::TRIGGER_CHARACTER
5399 }
5400 _ => CompletionTriggerKind::INVOKED,
5401 };
5402 let completion_context = CompletionContext {
5403 trigger_character: trigger.and_then(|trigger| {
5404 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5405 Some(String::from(trigger))
5406 } else {
5407 None
5408 }
5409 }),
5410 trigger_kind,
5411 };
5412
5413 let Anchor {
5414 excerpt_id: buffer_excerpt_id,
5415 text_anchor: buffer_position,
5416 ..
5417 } = buffer_position;
5418
5419 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5420 buffer_snapshot.surrounding_word(buffer_position, None)
5421 {
5422 let word_to_exclude = buffer_snapshot
5423 .text_for_range(word_range.clone())
5424 .collect::<String>();
5425 (
5426 buffer_snapshot.anchor_before(word_range.start)
5427 ..buffer_snapshot.anchor_after(buffer_position),
5428 Some(word_to_exclude),
5429 )
5430 } else {
5431 (buffer_position..buffer_position, None)
5432 };
5433
5434 let language = buffer_snapshot
5435 .language_at(buffer_position)
5436 .map(|language| language.name());
5437
5438 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5439 .completions
5440 .clone();
5441
5442 let show_completion_documentation = buffer_snapshot
5443 .settings_at(buffer_position, cx)
5444 .show_completion_documentation;
5445
5446 // The document can be large, so stay in reasonable bounds when searching for words,
5447 // otherwise completion pop-up might be slow to appear.
5448 const WORD_LOOKUP_ROWS: u32 = 5_000;
5449 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5450 let min_word_search = buffer_snapshot.clip_point(
5451 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5452 Bias::Left,
5453 );
5454 let max_word_search = buffer_snapshot.clip_point(
5455 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5456 Bias::Right,
5457 );
5458 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5459 ..buffer_snapshot.point_to_offset(max_word_search);
5460
5461 let skip_digits = query
5462 .as_ref()
5463 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5464
5465 let omit_word_completions = !self.word_completions_enabled
5466 || (!ignore_word_threshold
5467 && match &query {
5468 Some(query) => query.chars().count() < completion_settings.words_min_length,
5469 None => completion_settings.words_min_length != 0,
5470 });
5471
5472 let (mut words, provider_responses) = match &provider {
5473 Some(provider) => {
5474 let provider_responses = provider.completions(
5475 buffer_excerpt_id,
5476 &buffer,
5477 buffer_position,
5478 completion_context,
5479 window,
5480 cx,
5481 );
5482
5483 let words = match (omit_word_completions, completion_settings.words) {
5484 (true, _) | (_, WordsCompletionMode::Disabled) => {
5485 Task::ready(BTreeMap::default())
5486 }
5487 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5488 .background_spawn(async move {
5489 buffer_snapshot.words_in_range(WordsQuery {
5490 fuzzy_contents: None,
5491 range: word_search_range,
5492 skip_digits,
5493 })
5494 }),
5495 };
5496
5497 (words, provider_responses)
5498 }
5499 None => {
5500 let words = if omit_word_completions {
5501 Task::ready(BTreeMap::default())
5502 } else {
5503 cx.background_spawn(async move {
5504 buffer_snapshot.words_in_range(WordsQuery {
5505 fuzzy_contents: None,
5506 range: word_search_range,
5507 skip_digits,
5508 })
5509 })
5510 };
5511 (words, Task::ready(Ok(Vec::new())))
5512 }
5513 };
5514
5515 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5516
5517 let id = post_inc(&mut self.next_completion_id);
5518 let task = cx.spawn_in(window, async move |editor, cx| {
5519 let Ok(()) = editor.update(cx, |this, _| {
5520 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5521 }) else {
5522 return;
5523 };
5524
5525 // TODO: Ideally completions from different sources would be selectively re-queried, so
5526 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5527 let mut completions = Vec::new();
5528 let mut is_incomplete = false;
5529 let mut display_options: Option<CompletionDisplayOptions> = None;
5530 if let Some(provider_responses) = provider_responses.await.log_err()
5531 && !provider_responses.is_empty()
5532 {
5533 for response in provider_responses {
5534 completions.extend(response.completions);
5535 is_incomplete = is_incomplete || response.is_incomplete;
5536 match display_options.as_mut() {
5537 None => {
5538 display_options = Some(response.display_options);
5539 }
5540 Some(options) => options.merge(&response.display_options),
5541 }
5542 }
5543 if completion_settings.words == WordsCompletionMode::Fallback {
5544 words = Task::ready(BTreeMap::default());
5545 }
5546 }
5547 let display_options = display_options.unwrap_or_default();
5548
5549 let mut words = words.await;
5550 if let Some(word_to_exclude) = &word_to_exclude {
5551 words.remove(word_to_exclude);
5552 }
5553 for lsp_completion in &completions {
5554 words.remove(&lsp_completion.new_text);
5555 }
5556 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5557 replace_range: word_replace_range.clone(),
5558 new_text: word.clone(),
5559 label: CodeLabel::plain(word, None),
5560 icon_path: None,
5561 documentation: None,
5562 source: CompletionSource::BufferWord {
5563 word_range,
5564 resolved: false,
5565 },
5566 insert_text_mode: Some(InsertTextMode::AS_IS),
5567 confirm: None,
5568 }));
5569
5570 let menu = if completions.is_empty() {
5571 None
5572 } else {
5573 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5574 let languages = editor
5575 .workspace
5576 .as_ref()
5577 .and_then(|(workspace, _)| workspace.upgrade())
5578 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5579 let menu = CompletionsMenu::new(
5580 id,
5581 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5582 sort_completions,
5583 show_completion_documentation,
5584 position,
5585 query.clone(),
5586 is_incomplete,
5587 buffer.clone(),
5588 completions.into(),
5589 display_options,
5590 snippet_sort_order,
5591 languages,
5592 language,
5593 cx,
5594 );
5595
5596 let query = if filter_completions { query } else { None };
5597 let matches_task = if let Some(query) = query {
5598 menu.do_async_filtering(query, cx)
5599 } else {
5600 Task::ready(menu.unfiltered_matches())
5601 };
5602 (menu, matches_task)
5603 }) else {
5604 return;
5605 };
5606
5607 let matches = matches_task.await;
5608
5609 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5610 // Newer menu already set, so exit.
5611 if let Some(CodeContextMenu::Completions(prev_menu)) =
5612 editor.context_menu.borrow().as_ref()
5613 && prev_menu.id > id
5614 {
5615 return;
5616 };
5617
5618 // Only valid to take prev_menu because it the new menu is immediately set
5619 // below, or the menu is hidden.
5620 if let Some(CodeContextMenu::Completions(prev_menu)) =
5621 editor.context_menu.borrow_mut().take()
5622 {
5623 let position_matches =
5624 if prev_menu.initial_position == menu.initial_position {
5625 true
5626 } else {
5627 let snapshot = editor.buffer.read(cx).read(cx);
5628 prev_menu.initial_position.to_offset(&snapshot)
5629 == menu.initial_position.to_offset(&snapshot)
5630 };
5631 if position_matches {
5632 // Preserve markdown cache before `set_filter_results` because it will
5633 // try to populate the documentation cache.
5634 menu.preserve_markdown_cache(prev_menu);
5635 }
5636 };
5637
5638 menu.set_filter_results(matches, provider, window, cx);
5639 }) else {
5640 return;
5641 };
5642
5643 menu.visible().then_some(menu)
5644 };
5645
5646 editor
5647 .update_in(cx, |editor, window, cx| {
5648 if editor.focus_handle.is_focused(window)
5649 && let Some(menu) = menu
5650 {
5651 *editor.context_menu.borrow_mut() =
5652 Some(CodeContextMenu::Completions(menu));
5653
5654 crate::hover_popover::hide_hover(editor, cx);
5655 if editor.show_edit_predictions_in_menu() {
5656 editor.update_visible_edit_prediction(window, cx);
5657 } else {
5658 editor.discard_edit_prediction(false, cx);
5659 }
5660
5661 cx.notify();
5662 return;
5663 }
5664
5665 if editor.completion_tasks.len() <= 1 {
5666 // If there are no more completion tasks and the last menu was empty, we should hide it.
5667 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5668 // If it was already hidden and we don't show edit predictions in the menu,
5669 // we should also show the edit prediction when available.
5670 if was_hidden && editor.show_edit_predictions_in_menu() {
5671 editor.update_visible_edit_prediction(window, cx);
5672 }
5673 }
5674 })
5675 .ok();
5676 });
5677
5678 self.completion_tasks.push((id, task));
5679 }
5680
5681 #[cfg(feature = "test-support")]
5682 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5683 let menu = self.context_menu.borrow();
5684 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5685 let completions = menu.completions.borrow();
5686 Some(completions.to_vec())
5687 } else {
5688 None
5689 }
5690 }
5691
5692 pub fn with_completions_menu_matching_id<R>(
5693 &self,
5694 id: CompletionId,
5695 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5696 ) -> R {
5697 let mut context_menu = self.context_menu.borrow_mut();
5698 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5699 return f(None);
5700 };
5701 if completions_menu.id != id {
5702 return f(None);
5703 }
5704 f(Some(completions_menu))
5705 }
5706
5707 pub fn confirm_completion(
5708 &mut self,
5709 action: &ConfirmCompletion,
5710 window: &mut Window,
5711 cx: &mut Context<Self>,
5712 ) -> Option<Task<Result<()>>> {
5713 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5714 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5715 }
5716
5717 pub fn confirm_completion_insert(
5718 &mut self,
5719 _: &ConfirmCompletionInsert,
5720 window: &mut Window,
5721 cx: &mut Context<Self>,
5722 ) -> Option<Task<Result<()>>> {
5723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5724 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5725 }
5726
5727 pub fn confirm_completion_replace(
5728 &mut self,
5729 _: &ConfirmCompletionReplace,
5730 window: &mut Window,
5731 cx: &mut Context<Self>,
5732 ) -> Option<Task<Result<()>>> {
5733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5734 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5735 }
5736
5737 pub fn compose_completion(
5738 &mut self,
5739 action: &ComposeCompletion,
5740 window: &mut Window,
5741 cx: &mut Context<Self>,
5742 ) -> Option<Task<Result<()>>> {
5743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5744 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5745 }
5746
5747 fn do_completion(
5748 &mut self,
5749 item_ix: Option<usize>,
5750 intent: CompletionIntent,
5751 window: &mut Window,
5752 cx: &mut Context<Editor>,
5753 ) -> Option<Task<Result<()>>> {
5754 use language::ToOffset as _;
5755
5756 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5757 else {
5758 return None;
5759 };
5760
5761 let candidate_id = {
5762 let entries = completions_menu.entries.borrow();
5763 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5764 if self.show_edit_predictions_in_menu() {
5765 self.discard_edit_prediction(true, cx);
5766 }
5767 mat.candidate_id
5768 };
5769
5770 let completion = completions_menu
5771 .completions
5772 .borrow()
5773 .get(candidate_id)?
5774 .clone();
5775 cx.stop_propagation();
5776
5777 let buffer_handle = completions_menu.buffer.clone();
5778
5779 let CompletionEdit {
5780 new_text,
5781 snippet,
5782 replace_range,
5783 } = process_completion_for_edit(
5784 &completion,
5785 intent,
5786 &buffer_handle,
5787 &completions_menu.initial_position.text_anchor,
5788 cx,
5789 );
5790
5791 let buffer = buffer_handle.read(cx);
5792 let snapshot = self.buffer.read(cx).snapshot(cx);
5793 let newest_anchor = self.selections.newest_anchor();
5794 let replace_range_multibuffer = {
5795 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5796 excerpt.map_range_from_buffer(replace_range.clone())
5797 };
5798 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5799 return None;
5800 }
5801
5802 let old_text = buffer
5803 .text_for_range(replace_range.clone())
5804 .collect::<String>();
5805 let lookbehind = newest_anchor
5806 .start
5807 .text_anchor
5808 .to_offset(buffer)
5809 .saturating_sub(replace_range.start);
5810 let lookahead = replace_range
5811 .end
5812 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5813 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5814 let suffix = &old_text[lookbehind.min(old_text.len())..];
5815
5816 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5817 let mut ranges = Vec::new();
5818 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5819
5820 for selection in &selections {
5821 let range = if selection.id == newest_anchor.id {
5822 replace_range_multibuffer.clone()
5823 } else {
5824 let mut range = selection.range();
5825
5826 // if prefix is present, don't duplicate it
5827 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5828 range.start = range.start.saturating_sub(lookbehind);
5829
5830 // if suffix is also present, mimic the newest cursor and replace it
5831 if selection.id != newest_anchor.id
5832 && snapshot.contains_str_at(range.end, suffix)
5833 {
5834 range.end += lookahead;
5835 }
5836 }
5837 range
5838 };
5839
5840 ranges.push(range.clone());
5841
5842 if !self.linked_edit_ranges.is_empty() {
5843 let start_anchor = snapshot.anchor_before(range.start);
5844 let end_anchor = snapshot.anchor_after(range.end);
5845 if let Some(ranges) = self
5846 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5847 {
5848 for (buffer, edits) in ranges {
5849 linked_edits
5850 .entry(buffer.clone())
5851 .or_default()
5852 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5853 }
5854 }
5855 }
5856 }
5857
5858 let common_prefix_len = old_text
5859 .chars()
5860 .zip(new_text.chars())
5861 .take_while(|(a, b)| a == b)
5862 .map(|(a, _)| a.len_utf8())
5863 .sum::<usize>();
5864
5865 cx.emit(EditorEvent::InputHandled {
5866 utf16_range_to_replace: None,
5867 text: new_text[common_prefix_len..].into(),
5868 });
5869
5870 self.transact(window, cx, |editor, window, cx| {
5871 if let Some(mut snippet) = snippet {
5872 snippet.text = new_text.to_string();
5873 editor
5874 .insert_snippet(&ranges, snippet, window, cx)
5875 .log_err();
5876 } else {
5877 editor.buffer.update(cx, |multi_buffer, cx| {
5878 let auto_indent = match completion.insert_text_mode {
5879 Some(InsertTextMode::AS_IS) => None,
5880 _ => editor.autoindent_mode.clone(),
5881 };
5882 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5883 multi_buffer.edit(edits, auto_indent, cx);
5884 });
5885 }
5886 for (buffer, edits) in linked_edits {
5887 buffer.update(cx, |buffer, cx| {
5888 let snapshot = buffer.snapshot();
5889 let edits = edits
5890 .into_iter()
5891 .map(|(range, text)| {
5892 use text::ToPoint as TP;
5893 let end_point = TP::to_point(&range.end, &snapshot);
5894 let start_point = TP::to_point(&range.start, &snapshot);
5895 (start_point..end_point, text)
5896 })
5897 .sorted_by_key(|(range, _)| range.start);
5898 buffer.edit(edits, None, cx);
5899 })
5900 }
5901
5902 editor.refresh_edit_prediction(true, false, window, cx);
5903 });
5904 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5905
5906 let show_new_completions_on_confirm = completion
5907 .confirm
5908 .as_ref()
5909 .is_some_and(|confirm| confirm(intent, window, cx));
5910 if show_new_completions_on_confirm {
5911 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5912 }
5913
5914 let provider = self.completion_provider.as_ref()?;
5915 drop(completion);
5916 let apply_edits = provider.apply_additional_edits_for_completion(
5917 buffer_handle,
5918 completions_menu.completions.clone(),
5919 candidate_id,
5920 true,
5921 cx,
5922 );
5923
5924 let editor_settings = EditorSettings::get_global(cx);
5925 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5926 // After the code completion is finished, users often want to know what signatures are needed.
5927 // so we should automatically call signature_help
5928 self.show_signature_help(&ShowSignatureHelp, window, cx);
5929 }
5930
5931 Some(cx.foreground_executor().spawn(async move {
5932 apply_edits.await?;
5933 Ok(())
5934 }))
5935 }
5936
5937 pub fn toggle_code_actions(
5938 &mut self,
5939 action: &ToggleCodeActions,
5940 window: &mut Window,
5941 cx: &mut Context<Self>,
5942 ) {
5943 let quick_launch = action.quick_launch;
5944 let mut context_menu = self.context_menu.borrow_mut();
5945 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5946 if code_actions.deployed_from == action.deployed_from {
5947 // Toggle if we're selecting the same one
5948 *context_menu = None;
5949 cx.notify();
5950 return;
5951 } else {
5952 // Otherwise, clear it and start a new one
5953 *context_menu = None;
5954 cx.notify();
5955 }
5956 }
5957 drop(context_menu);
5958 let snapshot = self.snapshot(window, cx);
5959 let deployed_from = action.deployed_from.clone();
5960 let action = action.clone();
5961 self.completion_tasks.clear();
5962 self.discard_edit_prediction(false, cx);
5963
5964 let multibuffer_point = match &action.deployed_from {
5965 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5966 DisplayPoint::new(*row, 0).to_point(&snapshot)
5967 }
5968 _ => self
5969 .selections
5970 .newest::<Point>(&snapshot.display_snapshot)
5971 .head(),
5972 };
5973 let Some((buffer, buffer_row)) = snapshot
5974 .buffer_snapshot()
5975 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5976 .and_then(|(buffer_snapshot, range)| {
5977 self.buffer()
5978 .read(cx)
5979 .buffer(buffer_snapshot.remote_id())
5980 .map(|buffer| (buffer, range.start.row))
5981 })
5982 else {
5983 return;
5984 };
5985 let buffer_id = buffer.read(cx).remote_id();
5986 let tasks = self
5987 .tasks
5988 .get(&(buffer_id, buffer_row))
5989 .map(|t| Arc::new(t.to_owned()));
5990
5991 if !self.focus_handle.is_focused(window) {
5992 return;
5993 }
5994 let project = self.project.clone();
5995
5996 let code_actions_task = match deployed_from {
5997 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5998 _ => self.code_actions(buffer_row, window, cx),
5999 };
6000
6001 let runnable_task = match deployed_from {
6002 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6003 _ => {
6004 let mut task_context_task = Task::ready(None);
6005 if let Some(tasks) = &tasks
6006 && let Some(project) = project
6007 {
6008 task_context_task =
6009 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6010 }
6011
6012 cx.spawn_in(window, {
6013 let buffer = buffer.clone();
6014 async move |editor, cx| {
6015 let task_context = task_context_task.await;
6016
6017 let resolved_tasks =
6018 tasks
6019 .zip(task_context.clone())
6020 .map(|(tasks, task_context)| ResolvedTasks {
6021 templates: tasks.resolve(&task_context).collect(),
6022 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6023 multibuffer_point.row,
6024 tasks.column,
6025 )),
6026 });
6027 let debug_scenarios = editor
6028 .update(cx, |editor, cx| {
6029 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6030 })?
6031 .await;
6032 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6033 }
6034 })
6035 }
6036 };
6037
6038 cx.spawn_in(window, async move |editor, cx| {
6039 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6040 let code_actions = code_actions_task.await;
6041 let spawn_straight_away = quick_launch
6042 && resolved_tasks
6043 .as_ref()
6044 .is_some_and(|tasks| tasks.templates.len() == 1)
6045 && code_actions
6046 .as_ref()
6047 .is_none_or(|actions| actions.is_empty())
6048 && debug_scenarios.is_empty();
6049
6050 editor.update_in(cx, |editor, window, cx| {
6051 crate::hover_popover::hide_hover(editor, cx);
6052 let actions = CodeActionContents::new(
6053 resolved_tasks,
6054 code_actions,
6055 debug_scenarios,
6056 task_context.unwrap_or_default(),
6057 );
6058
6059 // Don't show the menu if there are no actions available
6060 if actions.is_empty() {
6061 cx.notify();
6062 return Task::ready(Ok(()));
6063 }
6064
6065 *editor.context_menu.borrow_mut() =
6066 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6067 buffer,
6068 actions,
6069 selected_item: Default::default(),
6070 scroll_handle: UniformListScrollHandle::default(),
6071 deployed_from,
6072 }));
6073 cx.notify();
6074 if spawn_straight_away
6075 && let Some(task) = editor.confirm_code_action(
6076 &ConfirmCodeAction { item_ix: Some(0) },
6077 window,
6078 cx,
6079 )
6080 {
6081 return task;
6082 }
6083
6084 Task::ready(Ok(()))
6085 })
6086 })
6087 .detach_and_log_err(cx);
6088 }
6089
6090 fn debug_scenarios(
6091 &mut self,
6092 resolved_tasks: &Option<ResolvedTasks>,
6093 buffer: &Entity<Buffer>,
6094 cx: &mut App,
6095 ) -> Task<Vec<task::DebugScenario>> {
6096 maybe!({
6097 let project = self.project()?;
6098 let dap_store = project.read(cx).dap_store();
6099 let mut scenarios = vec![];
6100 let resolved_tasks = resolved_tasks.as_ref()?;
6101 let buffer = buffer.read(cx);
6102 let language = buffer.language()?;
6103 let file = buffer.file();
6104 let debug_adapter = language_settings(language.name().into(), file, cx)
6105 .debuggers
6106 .first()
6107 .map(SharedString::from)
6108 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6109
6110 dap_store.update(cx, |dap_store, cx| {
6111 for (_, task) in &resolved_tasks.templates {
6112 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6113 task.original_task().clone(),
6114 debug_adapter.clone().into(),
6115 task.display_label().to_owned().into(),
6116 cx,
6117 );
6118 scenarios.push(maybe_scenario);
6119 }
6120 });
6121 Some(cx.background_spawn(async move {
6122 futures::future::join_all(scenarios)
6123 .await
6124 .into_iter()
6125 .flatten()
6126 .collect::<Vec<_>>()
6127 }))
6128 })
6129 .unwrap_or_else(|| Task::ready(vec![]))
6130 }
6131
6132 fn code_actions(
6133 &mut self,
6134 buffer_row: u32,
6135 window: &mut Window,
6136 cx: &mut Context<Self>,
6137 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6138 let mut task = self.code_actions_task.take();
6139 cx.spawn_in(window, async move |editor, cx| {
6140 while let Some(prev_task) = task {
6141 prev_task.await.log_err();
6142 task = editor
6143 .update(cx, |this, _| this.code_actions_task.take())
6144 .ok()?;
6145 }
6146
6147 editor
6148 .update(cx, |editor, cx| {
6149 editor
6150 .available_code_actions
6151 .clone()
6152 .and_then(|(location, code_actions)| {
6153 let snapshot = location.buffer.read(cx).snapshot();
6154 let point_range = location.range.to_point(&snapshot);
6155 let point_range = point_range.start.row..=point_range.end.row;
6156 if point_range.contains(&buffer_row) {
6157 Some(code_actions)
6158 } else {
6159 None
6160 }
6161 })
6162 })
6163 .ok()
6164 .flatten()
6165 })
6166 }
6167
6168 pub fn confirm_code_action(
6169 &mut self,
6170 action: &ConfirmCodeAction,
6171 window: &mut Window,
6172 cx: &mut Context<Self>,
6173 ) -> Option<Task<Result<()>>> {
6174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6175
6176 let actions_menu =
6177 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6178 menu
6179 } else {
6180 return None;
6181 };
6182
6183 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6184 let action = actions_menu.actions.get(action_ix)?;
6185 let title = action.label();
6186 let buffer = actions_menu.buffer;
6187 let workspace = self.workspace()?;
6188
6189 match action {
6190 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6191 workspace.update(cx, |workspace, cx| {
6192 workspace.schedule_resolved_task(
6193 task_source_kind,
6194 resolved_task,
6195 false,
6196 window,
6197 cx,
6198 );
6199
6200 Some(Task::ready(Ok(())))
6201 })
6202 }
6203 CodeActionsItem::CodeAction {
6204 excerpt_id,
6205 action,
6206 provider,
6207 } => {
6208 let apply_code_action =
6209 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6210 let workspace = workspace.downgrade();
6211 Some(cx.spawn_in(window, async move |editor, cx| {
6212 let project_transaction = apply_code_action.await?;
6213 Self::open_project_transaction(
6214 &editor,
6215 workspace,
6216 project_transaction,
6217 title,
6218 cx,
6219 )
6220 .await
6221 }))
6222 }
6223 CodeActionsItem::DebugScenario(scenario) => {
6224 let context = actions_menu.actions.context;
6225
6226 workspace.update(cx, |workspace, cx| {
6227 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6228 workspace.start_debug_session(
6229 scenario,
6230 context,
6231 Some(buffer),
6232 None,
6233 window,
6234 cx,
6235 );
6236 });
6237 Some(Task::ready(Ok(())))
6238 }
6239 }
6240 }
6241
6242 pub async fn open_project_transaction(
6243 editor: &WeakEntity<Editor>,
6244 workspace: WeakEntity<Workspace>,
6245 transaction: ProjectTransaction,
6246 title: String,
6247 cx: &mut AsyncWindowContext,
6248 ) -> Result<()> {
6249 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6250 cx.update(|_, cx| {
6251 entries.sort_unstable_by_key(|(buffer, _)| {
6252 buffer.read(cx).file().map(|f| f.path().clone())
6253 });
6254 })?;
6255 if entries.is_empty() {
6256 return Ok(());
6257 }
6258
6259 // If the project transaction's edits are all contained within this editor, then
6260 // avoid opening a new editor to display them.
6261
6262 if let [(buffer, transaction)] = &*entries {
6263 let excerpt = editor.update(cx, |editor, cx| {
6264 editor
6265 .buffer()
6266 .read(cx)
6267 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6268 })?;
6269 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6270 && excerpted_buffer == *buffer
6271 {
6272 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6273 let excerpt_range = excerpt_range.to_offset(buffer);
6274 buffer
6275 .edited_ranges_for_transaction::<usize>(transaction)
6276 .all(|range| {
6277 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6278 })
6279 })?;
6280
6281 if all_edits_within_excerpt {
6282 return Ok(());
6283 }
6284 }
6285 }
6286
6287 let mut ranges_to_highlight = Vec::new();
6288 let excerpt_buffer = cx.new(|cx| {
6289 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6290 for (buffer_handle, transaction) in &entries {
6291 let edited_ranges = buffer_handle
6292 .read(cx)
6293 .edited_ranges_for_transaction::<Point>(transaction)
6294 .collect::<Vec<_>>();
6295 let (ranges, _) = multibuffer.set_excerpts_for_path(
6296 PathKey::for_buffer(buffer_handle, cx),
6297 buffer_handle.clone(),
6298 edited_ranges,
6299 multibuffer_context_lines(cx),
6300 cx,
6301 );
6302
6303 ranges_to_highlight.extend(ranges);
6304 }
6305 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6306 multibuffer
6307 })?;
6308
6309 workspace.update_in(cx, |workspace, window, cx| {
6310 let project = workspace.project().clone();
6311 let editor =
6312 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6313 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6314 editor.update(cx, |editor, cx| {
6315 editor.highlight_background::<Self>(
6316 &ranges_to_highlight,
6317 |theme| theme.colors().editor_highlighted_line_background,
6318 cx,
6319 );
6320 });
6321 })?;
6322
6323 Ok(())
6324 }
6325
6326 pub fn clear_code_action_providers(&mut self) {
6327 self.code_action_providers.clear();
6328 self.available_code_actions.take();
6329 }
6330
6331 pub fn add_code_action_provider(
6332 &mut self,
6333 provider: Rc<dyn CodeActionProvider>,
6334 window: &mut Window,
6335 cx: &mut Context<Self>,
6336 ) {
6337 if self
6338 .code_action_providers
6339 .iter()
6340 .any(|existing_provider| existing_provider.id() == provider.id())
6341 {
6342 return;
6343 }
6344
6345 self.code_action_providers.push(provider);
6346 self.refresh_code_actions(window, cx);
6347 }
6348
6349 pub fn remove_code_action_provider(
6350 &mut self,
6351 id: Arc<str>,
6352 window: &mut Window,
6353 cx: &mut Context<Self>,
6354 ) {
6355 self.code_action_providers
6356 .retain(|provider| provider.id() != id);
6357 self.refresh_code_actions(window, cx);
6358 }
6359
6360 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6361 !self.code_action_providers.is_empty()
6362 && EditorSettings::get_global(cx).toolbar.code_actions
6363 }
6364
6365 pub fn has_available_code_actions(&self) -> bool {
6366 self.available_code_actions
6367 .as_ref()
6368 .is_some_and(|(_, actions)| !actions.is_empty())
6369 }
6370
6371 fn render_inline_code_actions(
6372 &self,
6373 icon_size: ui::IconSize,
6374 display_row: DisplayRow,
6375 is_active: bool,
6376 cx: &mut Context<Self>,
6377 ) -> AnyElement {
6378 let show_tooltip = !self.context_menu_visible();
6379 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6380 .icon_size(icon_size)
6381 .shape(ui::IconButtonShape::Square)
6382 .icon_color(ui::Color::Hidden)
6383 .toggle_state(is_active)
6384 .when(show_tooltip, |this| {
6385 this.tooltip({
6386 let focus_handle = self.focus_handle.clone();
6387 move |_window, cx| {
6388 Tooltip::for_action_in(
6389 "Toggle Code Actions",
6390 &ToggleCodeActions {
6391 deployed_from: None,
6392 quick_launch: false,
6393 },
6394 &focus_handle,
6395 cx,
6396 )
6397 }
6398 })
6399 })
6400 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6401 window.focus(&editor.focus_handle(cx));
6402 editor.toggle_code_actions(
6403 &crate::actions::ToggleCodeActions {
6404 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6405 display_row,
6406 )),
6407 quick_launch: false,
6408 },
6409 window,
6410 cx,
6411 );
6412 }))
6413 .into_any_element()
6414 }
6415
6416 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6417 &self.context_menu
6418 }
6419
6420 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6421 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6422 cx.background_executor()
6423 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6424 .await;
6425
6426 let (start_buffer, start, _, end, newest_selection) = this
6427 .update(cx, |this, cx| {
6428 let newest_selection = this.selections.newest_anchor().clone();
6429 if newest_selection.head().diff_base_anchor.is_some() {
6430 return None;
6431 }
6432 let display_snapshot = this.display_snapshot(cx);
6433 let newest_selection_adjusted =
6434 this.selections.newest_adjusted(&display_snapshot);
6435 let buffer = this.buffer.read(cx);
6436
6437 let (start_buffer, start) =
6438 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6439 let (end_buffer, end) =
6440 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6441
6442 Some((start_buffer, start, end_buffer, end, newest_selection))
6443 })?
6444 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6445 .context(
6446 "Expected selection to lie in a single buffer when refreshing code actions",
6447 )?;
6448 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6449 let providers = this.code_action_providers.clone();
6450 let tasks = this
6451 .code_action_providers
6452 .iter()
6453 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6454 .collect::<Vec<_>>();
6455 (providers, tasks)
6456 })?;
6457
6458 let mut actions = Vec::new();
6459 for (provider, provider_actions) in
6460 providers.into_iter().zip(future::join_all(tasks).await)
6461 {
6462 if let Some(provider_actions) = provider_actions.log_err() {
6463 actions.extend(provider_actions.into_iter().map(|action| {
6464 AvailableCodeAction {
6465 excerpt_id: newest_selection.start.excerpt_id,
6466 action,
6467 provider: provider.clone(),
6468 }
6469 }));
6470 }
6471 }
6472
6473 this.update(cx, |this, cx| {
6474 this.available_code_actions = if actions.is_empty() {
6475 None
6476 } else {
6477 Some((
6478 Location {
6479 buffer: start_buffer,
6480 range: start..end,
6481 },
6482 actions.into(),
6483 ))
6484 };
6485 cx.notify();
6486 })
6487 }));
6488 }
6489
6490 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6491 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6492 self.show_git_blame_inline = false;
6493
6494 self.show_git_blame_inline_delay_task =
6495 Some(cx.spawn_in(window, async move |this, cx| {
6496 cx.background_executor().timer(delay).await;
6497
6498 this.update(cx, |this, cx| {
6499 this.show_git_blame_inline = true;
6500 cx.notify();
6501 })
6502 .log_err();
6503 }));
6504 }
6505 }
6506
6507 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6508 let snapshot = self.snapshot(window, cx);
6509 let cursor = self
6510 .selections
6511 .newest::<Point>(&snapshot.display_snapshot)
6512 .head();
6513 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6514 else {
6515 return;
6516 };
6517
6518 let Some(blame) = self.blame.as_ref() else {
6519 return;
6520 };
6521
6522 let row_info = RowInfo {
6523 buffer_id: Some(buffer.remote_id()),
6524 buffer_row: Some(point.row),
6525 ..Default::default()
6526 };
6527 let Some((buffer, blame_entry)) = blame
6528 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6529 .flatten()
6530 else {
6531 return;
6532 };
6533
6534 let anchor = self.selections.newest_anchor().head();
6535 let position = self.to_pixel_point(anchor, &snapshot, window);
6536 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6537 self.show_blame_popover(
6538 buffer,
6539 &blame_entry,
6540 position + last_bounds.origin,
6541 true,
6542 cx,
6543 );
6544 };
6545 }
6546
6547 fn show_blame_popover(
6548 &mut self,
6549 buffer: BufferId,
6550 blame_entry: &BlameEntry,
6551 position: gpui::Point<Pixels>,
6552 ignore_timeout: bool,
6553 cx: &mut Context<Self>,
6554 ) {
6555 if let Some(state) = &mut self.inline_blame_popover {
6556 state.hide_task.take();
6557 } else {
6558 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6559 let blame_entry = blame_entry.clone();
6560 let show_task = cx.spawn(async move |editor, cx| {
6561 if !ignore_timeout {
6562 cx.background_executor()
6563 .timer(std::time::Duration::from_millis(blame_popover_delay))
6564 .await;
6565 }
6566 editor
6567 .update(cx, |editor, cx| {
6568 editor.inline_blame_popover_show_task.take();
6569 let Some(blame) = editor.blame.as_ref() else {
6570 return;
6571 };
6572 let blame = blame.read(cx);
6573 let details = blame.details_for_entry(buffer, &blame_entry);
6574 let markdown = cx.new(|cx| {
6575 Markdown::new(
6576 details
6577 .as_ref()
6578 .map(|message| message.message.clone())
6579 .unwrap_or_default(),
6580 None,
6581 None,
6582 cx,
6583 )
6584 });
6585 editor.inline_blame_popover = Some(InlineBlamePopover {
6586 position,
6587 hide_task: None,
6588 popover_bounds: None,
6589 popover_state: InlineBlamePopoverState {
6590 scroll_handle: ScrollHandle::new(),
6591 commit_message: details,
6592 markdown,
6593 },
6594 keyboard_grace: ignore_timeout,
6595 });
6596 cx.notify();
6597 })
6598 .ok();
6599 });
6600 self.inline_blame_popover_show_task = Some(show_task);
6601 }
6602 }
6603
6604 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6605 self.inline_blame_popover_show_task.take();
6606 if let Some(state) = &mut self.inline_blame_popover {
6607 let hide_task = cx.spawn(async move |editor, cx| {
6608 if !ignore_timeout {
6609 cx.background_executor()
6610 .timer(std::time::Duration::from_millis(100))
6611 .await;
6612 }
6613 editor
6614 .update(cx, |editor, cx| {
6615 editor.inline_blame_popover.take();
6616 cx.notify();
6617 })
6618 .ok();
6619 });
6620 state.hide_task = Some(hide_task);
6621 true
6622 } else {
6623 false
6624 }
6625 }
6626
6627 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6628 if self.pending_rename.is_some() {
6629 return None;
6630 }
6631
6632 let provider = self.semantics_provider.clone()?;
6633 let buffer = self.buffer.read(cx);
6634 let newest_selection = self.selections.newest_anchor().clone();
6635 let cursor_position = newest_selection.head();
6636 let (cursor_buffer, cursor_buffer_position) =
6637 buffer.text_anchor_for_position(cursor_position, cx)?;
6638 let (tail_buffer, tail_buffer_position) =
6639 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6640 if cursor_buffer != tail_buffer {
6641 return None;
6642 }
6643
6644 let snapshot = cursor_buffer.read(cx).snapshot();
6645 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6646 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6647 if start_word_range != end_word_range {
6648 self.document_highlights_task.take();
6649 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6650 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6651 return None;
6652 }
6653
6654 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6655 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6656 cx.background_executor()
6657 .timer(Duration::from_millis(debounce))
6658 .await;
6659
6660 let highlights = if let Some(highlights) = cx
6661 .update(|cx| {
6662 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6663 })
6664 .ok()
6665 .flatten()
6666 {
6667 highlights.await.log_err()
6668 } else {
6669 None
6670 };
6671
6672 if let Some(highlights) = highlights {
6673 this.update(cx, |this, cx| {
6674 if this.pending_rename.is_some() {
6675 return;
6676 }
6677
6678 let buffer = this.buffer.read(cx);
6679 if buffer
6680 .text_anchor_for_position(cursor_position, cx)
6681 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6682 {
6683 return;
6684 }
6685
6686 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6687 let mut write_ranges = Vec::new();
6688 let mut read_ranges = Vec::new();
6689 for highlight in highlights {
6690 let buffer_id = cursor_buffer.read(cx).remote_id();
6691 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6692 {
6693 let start = highlight
6694 .range
6695 .start
6696 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6697 let end = highlight
6698 .range
6699 .end
6700 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6701 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6702 continue;
6703 }
6704
6705 let range =
6706 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6707 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6708 write_ranges.push(range);
6709 } else {
6710 read_ranges.push(range);
6711 }
6712 }
6713 }
6714
6715 this.highlight_background::<DocumentHighlightRead>(
6716 &read_ranges,
6717 |theme| theme.colors().editor_document_highlight_read_background,
6718 cx,
6719 );
6720 this.highlight_background::<DocumentHighlightWrite>(
6721 &write_ranges,
6722 |theme| theme.colors().editor_document_highlight_write_background,
6723 cx,
6724 );
6725 cx.notify();
6726 })
6727 .log_err();
6728 }
6729 }));
6730 None
6731 }
6732
6733 fn prepare_highlight_query_from_selection(
6734 &mut self,
6735 window: &Window,
6736 cx: &mut Context<Editor>,
6737 ) -> Option<(String, Range<Anchor>)> {
6738 if matches!(self.mode, EditorMode::SingleLine) {
6739 return None;
6740 }
6741 if !EditorSettings::get_global(cx).selection_highlight {
6742 return None;
6743 }
6744 if self.selections.count() != 1 || self.selections.line_mode() {
6745 return None;
6746 }
6747 let snapshot = self.snapshot(window, cx);
6748 let selection = self.selections.newest::<Point>(&snapshot);
6749 // If the selection spans multiple rows OR it is empty
6750 if selection.start.row != selection.end.row
6751 || selection.start.column == selection.end.column
6752 {
6753 return None;
6754 }
6755 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6756 let query = snapshot
6757 .buffer_snapshot()
6758 .text_for_range(selection_anchor_range.clone())
6759 .collect::<String>();
6760 if query.trim().is_empty() {
6761 return None;
6762 }
6763 Some((query, selection_anchor_range))
6764 }
6765
6766 fn update_selection_occurrence_highlights(
6767 &mut self,
6768 query_text: String,
6769 query_range: Range<Anchor>,
6770 multi_buffer_range_to_query: Range<Point>,
6771 use_debounce: bool,
6772 window: &mut Window,
6773 cx: &mut Context<Editor>,
6774 ) -> Task<()> {
6775 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6776 cx.spawn_in(window, async move |editor, cx| {
6777 if use_debounce {
6778 cx.background_executor()
6779 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6780 .await;
6781 }
6782 let match_task = cx.background_spawn(async move {
6783 let buffer_ranges = multi_buffer_snapshot
6784 .range_to_buffer_ranges(multi_buffer_range_to_query)
6785 .into_iter()
6786 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6787 let mut match_ranges = Vec::new();
6788 let Ok(regex) = project::search::SearchQuery::text(
6789 query_text.clone(),
6790 false,
6791 false,
6792 false,
6793 Default::default(),
6794 Default::default(),
6795 false,
6796 None,
6797 ) else {
6798 return Vec::default();
6799 };
6800 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6801 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6802 match_ranges.extend(
6803 regex
6804 .search(buffer_snapshot, Some(search_range.clone()))
6805 .await
6806 .into_iter()
6807 .filter_map(|match_range| {
6808 let match_start = buffer_snapshot
6809 .anchor_after(search_range.start + match_range.start);
6810 let match_end = buffer_snapshot
6811 .anchor_before(search_range.start + match_range.end);
6812 let match_anchor_range = Anchor::range_in_buffer(
6813 excerpt_id,
6814 buffer_snapshot.remote_id(),
6815 match_start..match_end,
6816 );
6817 (match_anchor_range != query_range).then_some(match_anchor_range)
6818 }),
6819 );
6820 }
6821 match_ranges
6822 });
6823 let match_ranges = match_task.await;
6824 editor
6825 .update_in(cx, |editor, _, cx| {
6826 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6827 if !match_ranges.is_empty() {
6828 editor.highlight_background::<SelectedTextHighlight>(
6829 &match_ranges,
6830 |theme| theme.colors().editor_document_highlight_bracket_background,
6831 cx,
6832 )
6833 }
6834 })
6835 .log_err();
6836 })
6837 }
6838
6839 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6840 struct NewlineFold;
6841 let type_id = std::any::TypeId::of::<NewlineFold>();
6842 if !self.mode.is_single_line() {
6843 return;
6844 }
6845 let snapshot = self.snapshot(window, cx);
6846 if snapshot.buffer_snapshot().max_point().row == 0 {
6847 return;
6848 }
6849 let task = cx.background_spawn(async move {
6850 let new_newlines = snapshot
6851 .buffer_chars_at(0)
6852 .filter_map(|(c, i)| {
6853 if c == '\n' {
6854 Some(
6855 snapshot.buffer_snapshot().anchor_after(i)
6856 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6857 )
6858 } else {
6859 None
6860 }
6861 })
6862 .collect::<Vec<_>>();
6863 let existing_newlines = snapshot
6864 .folds_in_range(0..snapshot.buffer_snapshot().len())
6865 .filter_map(|fold| {
6866 if fold.placeholder.type_tag == Some(type_id) {
6867 Some(fold.range.start..fold.range.end)
6868 } else {
6869 None
6870 }
6871 })
6872 .collect::<Vec<_>>();
6873
6874 (new_newlines, existing_newlines)
6875 });
6876 self.folding_newlines = cx.spawn(async move |this, cx| {
6877 let (new_newlines, existing_newlines) = task.await;
6878 if new_newlines == existing_newlines {
6879 return;
6880 }
6881 let placeholder = FoldPlaceholder {
6882 render: Arc::new(move |_, _, cx| {
6883 div()
6884 .bg(cx.theme().status().hint_background)
6885 .border_b_1()
6886 .size_full()
6887 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6888 .border_color(cx.theme().status().hint)
6889 .child("\\n")
6890 .into_any()
6891 }),
6892 constrain_width: false,
6893 merge_adjacent: false,
6894 type_tag: Some(type_id),
6895 };
6896 let creases = new_newlines
6897 .into_iter()
6898 .map(|range| Crease::simple(range, placeholder.clone()))
6899 .collect();
6900 this.update(cx, |this, cx| {
6901 this.display_map.update(cx, |display_map, cx| {
6902 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6903 display_map.fold(creases, cx);
6904 });
6905 })
6906 .ok();
6907 });
6908 }
6909
6910 fn refresh_selected_text_highlights(
6911 &mut self,
6912 on_buffer_edit: bool,
6913 window: &mut Window,
6914 cx: &mut Context<Editor>,
6915 ) {
6916 let Some((query_text, query_range)) =
6917 self.prepare_highlight_query_from_selection(window, cx)
6918 else {
6919 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6920 self.quick_selection_highlight_task.take();
6921 self.debounced_selection_highlight_task.take();
6922 return;
6923 };
6924 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6925 if on_buffer_edit
6926 || self
6927 .quick_selection_highlight_task
6928 .as_ref()
6929 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6930 {
6931 let multi_buffer_visible_start = self
6932 .scroll_manager
6933 .anchor()
6934 .anchor
6935 .to_point(&multi_buffer_snapshot);
6936 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6937 multi_buffer_visible_start
6938 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6939 Bias::Left,
6940 );
6941 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6942 self.quick_selection_highlight_task = Some((
6943 query_range.clone(),
6944 self.update_selection_occurrence_highlights(
6945 query_text.clone(),
6946 query_range.clone(),
6947 multi_buffer_visible_range,
6948 false,
6949 window,
6950 cx,
6951 ),
6952 ));
6953 }
6954 if on_buffer_edit
6955 || self
6956 .debounced_selection_highlight_task
6957 .as_ref()
6958 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6959 {
6960 let multi_buffer_start = multi_buffer_snapshot
6961 .anchor_before(0)
6962 .to_point(&multi_buffer_snapshot);
6963 let multi_buffer_end = multi_buffer_snapshot
6964 .anchor_after(multi_buffer_snapshot.len())
6965 .to_point(&multi_buffer_snapshot);
6966 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6967 self.debounced_selection_highlight_task = Some((
6968 query_range.clone(),
6969 self.update_selection_occurrence_highlights(
6970 query_text,
6971 query_range,
6972 multi_buffer_full_range,
6973 true,
6974 window,
6975 cx,
6976 ),
6977 ));
6978 }
6979 }
6980
6981 pub fn refresh_edit_prediction(
6982 &mut self,
6983 debounce: bool,
6984 user_requested: bool,
6985 window: &mut Window,
6986 cx: &mut Context<Self>,
6987 ) -> Option<()> {
6988 if DisableAiSettings::get_global(cx).disable_ai {
6989 return None;
6990 }
6991
6992 let provider = self.edit_prediction_provider()?;
6993 let cursor = self.selections.newest_anchor().head();
6994 let (buffer, cursor_buffer_position) =
6995 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6996
6997 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6998 self.discard_edit_prediction(false, cx);
6999 return None;
7000 }
7001
7002 self.update_visible_edit_prediction(window, cx);
7003
7004 if !user_requested
7005 && (!self.should_show_edit_predictions()
7006 || !self.is_focused(window)
7007 || buffer.read(cx).is_empty())
7008 {
7009 self.discard_edit_prediction(false, cx);
7010 return None;
7011 }
7012
7013 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7014 Some(())
7015 }
7016
7017 fn show_edit_predictions_in_menu(&self) -> bool {
7018 match self.edit_prediction_settings {
7019 EditPredictionSettings::Disabled => false,
7020 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7021 }
7022 }
7023
7024 pub fn edit_predictions_enabled(&self) -> bool {
7025 match self.edit_prediction_settings {
7026 EditPredictionSettings::Disabled => false,
7027 EditPredictionSettings::Enabled { .. } => true,
7028 }
7029 }
7030
7031 fn edit_prediction_requires_modifier(&self) -> bool {
7032 match self.edit_prediction_settings {
7033 EditPredictionSettings::Disabled => false,
7034 EditPredictionSettings::Enabled {
7035 preview_requires_modifier,
7036 ..
7037 } => preview_requires_modifier,
7038 }
7039 }
7040
7041 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7042 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7043 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7044 self.discard_edit_prediction(false, cx);
7045 } else {
7046 let selection = self.selections.newest_anchor();
7047 let cursor = selection.head();
7048
7049 if let Some((buffer, cursor_buffer_position)) =
7050 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7051 {
7052 self.edit_prediction_settings =
7053 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7054 }
7055 }
7056 }
7057
7058 fn edit_prediction_settings_at_position(
7059 &self,
7060 buffer: &Entity<Buffer>,
7061 buffer_position: language::Anchor,
7062 cx: &App,
7063 ) -> EditPredictionSettings {
7064 if !self.mode.is_full()
7065 || !self.show_edit_predictions_override.unwrap_or(true)
7066 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7067 {
7068 return EditPredictionSettings::Disabled;
7069 }
7070
7071 let buffer = buffer.read(cx);
7072
7073 let file = buffer.file();
7074
7075 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7076 return EditPredictionSettings::Disabled;
7077 };
7078
7079 let by_provider = matches!(
7080 self.menu_edit_predictions_policy,
7081 MenuEditPredictionsPolicy::ByProvider
7082 );
7083
7084 let show_in_menu = by_provider
7085 && self
7086 .edit_prediction_provider
7087 .as_ref()
7088 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7089
7090 let preview_requires_modifier =
7091 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7092
7093 EditPredictionSettings::Enabled {
7094 show_in_menu,
7095 preview_requires_modifier,
7096 }
7097 }
7098
7099 fn should_show_edit_predictions(&self) -> bool {
7100 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7101 }
7102
7103 pub fn edit_prediction_preview_is_active(&self) -> bool {
7104 matches!(
7105 self.edit_prediction_preview,
7106 EditPredictionPreview::Active { .. }
7107 )
7108 }
7109
7110 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7111 let cursor = self.selections.newest_anchor().head();
7112 if let Some((buffer, cursor_position)) =
7113 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7114 {
7115 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7116 } else {
7117 false
7118 }
7119 }
7120
7121 pub fn supports_minimap(&self, cx: &App) -> bool {
7122 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7123 }
7124
7125 fn edit_predictions_enabled_in_buffer(
7126 &self,
7127 buffer: &Entity<Buffer>,
7128 buffer_position: language::Anchor,
7129 cx: &App,
7130 ) -> bool {
7131 maybe!({
7132 if self.read_only(cx) {
7133 return Some(false);
7134 }
7135 let provider = self.edit_prediction_provider()?;
7136 if !provider.is_enabled(buffer, buffer_position, cx) {
7137 return Some(false);
7138 }
7139 let buffer = buffer.read(cx);
7140 let Some(file) = buffer.file() else {
7141 return Some(true);
7142 };
7143 let settings = all_language_settings(Some(file), cx);
7144 Some(settings.edit_predictions_enabled_for_file(file, cx))
7145 })
7146 .unwrap_or(false)
7147 }
7148
7149 fn cycle_edit_prediction(
7150 &mut self,
7151 direction: Direction,
7152 window: &mut Window,
7153 cx: &mut Context<Self>,
7154 ) -> Option<()> {
7155 let provider = self.edit_prediction_provider()?;
7156 let cursor = self.selections.newest_anchor().head();
7157 let (buffer, cursor_buffer_position) =
7158 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7159 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7160 return None;
7161 }
7162
7163 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7164 self.update_visible_edit_prediction(window, cx);
7165
7166 Some(())
7167 }
7168
7169 pub fn show_edit_prediction(
7170 &mut self,
7171 _: &ShowEditPrediction,
7172 window: &mut Window,
7173 cx: &mut Context<Self>,
7174 ) {
7175 if !self.has_active_edit_prediction() {
7176 self.refresh_edit_prediction(false, true, window, cx);
7177 return;
7178 }
7179
7180 self.update_visible_edit_prediction(window, cx);
7181 }
7182
7183 pub fn display_cursor_names(
7184 &mut self,
7185 _: &DisplayCursorNames,
7186 window: &mut Window,
7187 cx: &mut Context<Self>,
7188 ) {
7189 self.show_cursor_names(window, cx);
7190 }
7191
7192 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7193 self.show_cursor_names = true;
7194 cx.notify();
7195 cx.spawn_in(window, async move |this, cx| {
7196 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7197 this.update(cx, |this, cx| {
7198 this.show_cursor_names = false;
7199 cx.notify()
7200 })
7201 .ok()
7202 })
7203 .detach();
7204 }
7205
7206 pub fn next_edit_prediction(
7207 &mut self,
7208 _: &NextEditPrediction,
7209 window: &mut Window,
7210 cx: &mut Context<Self>,
7211 ) {
7212 if self.has_active_edit_prediction() {
7213 self.cycle_edit_prediction(Direction::Next, window, cx);
7214 } else {
7215 let is_copilot_disabled = self
7216 .refresh_edit_prediction(false, true, window, cx)
7217 .is_none();
7218 if is_copilot_disabled {
7219 cx.propagate();
7220 }
7221 }
7222 }
7223
7224 pub fn previous_edit_prediction(
7225 &mut self,
7226 _: &PreviousEditPrediction,
7227 window: &mut Window,
7228 cx: &mut Context<Self>,
7229 ) {
7230 if self.has_active_edit_prediction() {
7231 self.cycle_edit_prediction(Direction::Prev, window, cx);
7232 } else {
7233 let is_copilot_disabled = self
7234 .refresh_edit_prediction(false, true, window, cx)
7235 .is_none();
7236 if is_copilot_disabled {
7237 cx.propagate();
7238 }
7239 }
7240 }
7241
7242 pub fn accept_edit_prediction(
7243 &mut self,
7244 _: &AcceptEditPrediction,
7245 window: &mut Window,
7246 cx: &mut Context<Self>,
7247 ) {
7248 if self.show_edit_predictions_in_menu() {
7249 self.hide_context_menu(window, cx);
7250 }
7251
7252 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7253 return;
7254 };
7255
7256 match &active_edit_prediction.completion {
7257 EditPrediction::MoveWithin { target, .. } => {
7258 let target = *target;
7259
7260 if let Some(position_map) = &self.last_position_map {
7261 if position_map
7262 .visible_row_range
7263 .contains(&target.to_display_point(&position_map.snapshot).row())
7264 || !self.edit_prediction_requires_modifier()
7265 {
7266 self.unfold_ranges(&[target..target], true, false, cx);
7267 // Note that this is also done in vim's handler of the Tab action.
7268 self.change_selections(
7269 SelectionEffects::scroll(Autoscroll::newest()),
7270 window,
7271 cx,
7272 |selections| {
7273 selections.select_anchor_ranges([target..target]);
7274 },
7275 );
7276 self.clear_row_highlights::<EditPredictionPreview>();
7277
7278 self.edit_prediction_preview
7279 .set_previous_scroll_position(None);
7280 } else {
7281 self.edit_prediction_preview
7282 .set_previous_scroll_position(Some(
7283 position_map.snapshot.scroll_anchor,
7284 ));
7285
7286 self.highlight_rows::<EditPredictionPreview>(
7287 target..target,
7288 cx.theme().colors().editor_highlighted_line_background,
7289 RowHighlightOptions {
7290 autoscroll: true,
7291 ..Default::default()
7292 },
7293 cx,
7294 );
7295 self.request_autoscroll(Autoscroll::fit(), cx);
7296 }
7297 }
7298 }
7299 EditPrediction::MoveOutside { snapshot, target } => {
7300 if let Some(workspace) = self.workspace() {
7301 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7302 .detach_and_log_err(cx);
7303 }
7304 }
7305 EditPrediction::Edit { edits, .. } => {
7306 self.report_edit_prediction_event(
7307 active_edit_prediction.completion_id.clone(),
7308 true,
7309 cx,
7310 );
7311
7312 if let Some(provider) = self.edit_prediction_provider() {
7313 provider.accept(cx);
7314 }
7315
7316 // Store the transaction ID and selections before applying the edit
7317 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7318
7319 let snapshot = self.buffer.read(cx).snapshot(cx);
7320 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7321
7322 self.buffer.update(cx, |buffer, cx| {
7323 buffer.edit(edits.iter().cloned(), None, cx)
7324 });
7325
7326 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7327 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7328 });
7329
7330 let selections = self.selections.disjoint_anchors_arc();
7331 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7332 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7333 if has_new_transaction {
7334 self.selection_history
7335 .insert_transaction(transaction_id_now, selections);
7336 }
7337 }
7338
7339 self.update_visible_edit_prediction(window, cx);
7340 if self.active_edit_prediction.is_none() {
7341 self.refresh_edit_prediction(true, true, window, cx);
7342 }
7343
7344 cx.notify();
7345 }
7346 }
7347
7348 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7349 }
7350
7351 pub fn accept_partial_edit_prediction(
7352 &mut self,
7353 _: &AcceptPartialEditPrediction,
7354 window: &mut Window,
7355 cx: &mut Context<Self>,
7356 ) {
7357 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7358 return;
7359 };
7360 if self.selections.count() != 1 {
7361 return;
7362 }
7363
7364 match &active_edit_prediction.completion {
7365 EditPrediction::MoveWithin { target, .. } => {
7366 let target = *target;
7367 self.change_selections(
7368 SelectionEffects::scroll(Autoscroll::newest()),
7369 window,
7370 cx,
7371 |selections| {
7372 selections.select_anchor_ranges([target..target]);
7373 },
7374 );
7375 }
7376 EditPrediction::MoveOutside { snapshot, target } => {
7377 if let Some(workspace) = self.workspace() {
7378 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7379 .detach_and_log_err(cx);
7380 }
7381 }
7382 EditPrediction::Edit { edits, .. } => {
7383 self.report_edit_prediction_event(
7384 active_edit_prediction.completion_id.clone(),
7385 true,
7386 cx,
7387 );
7388
7389 // Find an insertion that starts at the cursor position.
7390 let snapshot = self.buffer.read(cx).snapshot(cx);
7391 let cursor_offset = self
7392 .selections
7393 .newest::<usize>(&self.display_snapshot(cx))
7394 .head();
7395 let insertion = edits.iter().find_map(|(range, text)| {
7396 let range = range.to_offset(&snapshot);
7397 if range.is_empty() && range.start == cursor_offset {
7398 Some(text)
7399 } else {
7400 None
7401 }
7402 });
7403
7404 if let Some(text) = insertion {
7405 let mut partial_completion = text
7406 .chars()
7407 .by_ref()
7408 .take_while(|c| c.is_alphabetic())
7409 .collect::<String>();
7410 if partial_completion.is_empty() {
7411 partial_completion = text
7412 .chars()
7413 .by_ref()
7414 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7415 .collect::<String>();
7416 }
7417
7418 cx.emit(EditorEvent::InputHandled {
7419 utf16_range_to_replace: None,
7420 text: partial_completion.clone().into(),
7421 });
7422
7423 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7424
7425 self.refresh_edit_prediction(true, true, window, cx);
7426 cx.notify();
7427 } else {
7428 self.accept_edit_prediction(&Default::default(), window, cx);
7429 }
7430 }
7431 }
7432 }
7433
7434 fn discard_edit_prediction(
7435 &mut self,
7436 should_report_edit_prediction_event: bool,
7437 cx: &mut Context<Self>,
7438 ) -> bool {
7439 if should_report_edit_prediction_event {
7440 let completion_id = self
7441 .active_edit_prediction
7442 .as_ref()
7443 .and_then(|active_completion| active_completion.completion_id.clone());
7444
7445 self.report_edit_prediction_event(completion_id, false, cx);
7446 }
7447
7448 if let Some(provider) = self.edit_prediction_provider() {
7449 provider.discard(cx);
7450 }
7451
7452 self.take_active_edit_prediction(cx)
7453 }
7454
7455 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7456 let Some(provider) = self.edit_prediction_provider() else {
7457 return;
7458 };
7459
7460 let Some((_, buffer, _)) = self
7461 .buffer
7462 .read(cx)
7463 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7464 else {
7465 return;
7466 };
7467
7468 let extension = buffer
7469 .read(cx)
7470 .file()
7471 .and_then(|file| Some(file.path().extension()?.to_string()));
7472
7473 let event_type = match accepted {
7474 true => "Edit Prediction Accepted",
7475 false => "Edit Prediction Discarded",
7476 };
7477 telemetry::event!(
7478 event_type,
7479 provider = provider.name(),
7480 prediction_id = id,
7481 suggestion_accepted = accepted,
7482 file_extension = extension,
7483 );
7484 }
7485
7486 fn open_editor_at_anchor(
7487 snapshot: &language::BufferSnapshot,
7488 target: language::Anchor,
7489 workspace: &Entity<Workspace>,
7490 window: &mut Window,
7491 cx: &mut App,
7492 ) -> Task<Result<()>> {
7493 workspace.update(cx, |workspace, cx| {
7494 let path = snapshot.file().map(|file| file.full_path(cx));
7495 let Some(path) =
7496 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7497 else {
7498 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7499 };
7500 let target = text::ToPoint::to_point(&target, snapshot);
7501 let item = workspace.open_path(path, None, true, window, cx);
7502 window.spawn(cx, async move |cx| {
7503 let Some(editor) = item.await?.downcast::<Editor>() else {
7504 return Ok(());
7505 };
7506 editor
7507 .update_in(cx, |editor, window, cx| {
7508 editor.go_to_singleton_buffer_point(target, window, cx);
7509 })
7510 .ok();
7511 anyhow::Ok(())
7512 })
7513 })
7514 }
7515
7516 pub fn has_active_edit_prediction(&self) -> bool {
7517 self.active_edit_prediction.is_some()
7518 }
7519
7520 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7521 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7522 return false;
7523 };
7524
7525 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7526 self.clear_highlights::<EditPredictionHighlight>(cx);
7527 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7528 true
7529 }
7530
7531 /// Returns true when we're displaying the edit prediction popover below the cursor
7532 /// like we are not previewing and the LSP autocomplete menu is visible
7533 /// or we are in `when_holding_modifier` mode.
7534 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7535 if self.edit_prediction_preview_is_active()
7536 || !self.show_edit_predictions_in_menu()
7537 || !self.edit_predictions_enabled()
7538 {
7539 return false;
7540 }
7541
7542 if self.has_visible_completions_menu() {
7543 return true;
7544 }
7545
7546 has_completion && self.edit_prediction_requires_modifier()
7547 }
7548
7549 fn handle_modifiers_changed(
7550 &mut self,
7551 modifiers: Modifiers,
7552 position_map: &PositionMap,
7553 window: &mut Window,
7554 cx: &mut Context<Self>,
7555 ) {
7556 if self.show_edit_predictions_in_menu() {
7557 self.update_edit_prediction_preview(&modifiers, window, cx);
7558 }
7559
7560 self.update_selection_mode(&modifiers, position_map, window, cx);
7561
7562 let mouse_position = window.mouse_position();
7563 if !position_map.text_hitbox.is_hovered(window) {
7564 return;
7565 }
7566
7567 self.update_hovered_link(
7568 position_map.point_for_position(mouse_position),
7569 &position_map.snapshot,
7570 modifiers,
7571 window,
7572 cx,
7573 )
7574 }
7575
7576 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7577 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7578 if invert {
7579 match multi_cursor_setting {
7580 MultiCursorModifier::Alt => modifiers.alt,
7581 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7582 }
7583 } else {
7584 match multi_cursor_setting {
7585 MultiCursorModifier::Alt => modifiers.secondary(),
7586 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7587 }
7588 }
7589 }
7590
7591 fn columnar_selection_mode(
7592 modifiers: &Modifiers,
7593 cx: &mut Context<Self>,
7594 ) -> Option<ColumnarMode> {
7595 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7596 if Self::multi_cursor_modifier(false, modifiers, cx) {
7597 Some(ColumnarMode::FromMouse)
7598 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7599 Some(ColumnarMode::FromSelection)
7600 } else {
7601 None
7602 }
7603 } else {
7604 None
7605 }
7606 }
7607
7608 fn update_selection_mode(
7609 &mut self,
7610 modifiers: &Modifiers,
7611 position_map: &PositionMap,
7612 window: &mut Window,
7613 cx: &mut Context<Self>,
7614 ) {
7615 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7616 return;
7617 };
7618 if self.selections.pending_anchor().is_none() {
7619 return;
7620 }
7621
7622 let mouse_position = window.mouse_position();
7623 let point_for_position = position_map.point_for_position(mouse_position);
7624 let position = point_for_position.previous_valid;
7625
7626 self.select(
7627 SelectPhase::BeginColumnar {
7628 position,
7629 reset: false,
7630 mode,
7631 goal_column: point_for_position.exact_unclipped.column(),
7632 },
7633 window,
7634 cx,
7635 );
7636 }
7637
7638 fn update_edit_prediction_preview(
7639 &mut self,
7640 modifiers: &Modifiers,
7641 window: &mut Window,
7642 cx: &mut Context<Self>,
7643 ) {
7644 let mut modifiers_held = false;
7645 if let Some(accept_keystroke) = self
7646 .accept_edit_prediction_keybind(false, window, cx)
7647 .keystroke()
7648 {
7649 modifiers_held = modifiers_held
7650 || (accept_keystroke.modifiers() == modifiers
7651 && accept_keystroke.modifiers().modified());
7652 };
7653 if let Some(accept_partial_keystroke) = self
7654 .accept_edit_prediction_keybind(true, window, cx)
7655 .keystroke()
7656 {
7657 modifiers_held = modifiers_held
7658 || (accept_partial_keystroke.modifiers() == modifiers
7659 && accept_partial_keystroke.modifiers().modified());
7660 }
7661
7662 if modifiers_held {
7663 if matches!(
7664 self.edit_prediction_preview,
7665 EditPredictionPreview::Inactive { .. }
7666 ) {
7667 self.edit_prediction_preview = EditPredictionPreview::Active {
7668 previous_scroll_position: None,
7669 since: Instant::now(),
7670 };
7671
7672 self.update_visible_edit_prediction(window, cx);
7673 cx.notify();
7674 }
7675 } else if let EditPredictionPreview::Active {
7676 previous_scroll_position,
7677 since,
7678 } = self.edit_prediction_preview
7679 {
7680 if let (Some(previous_scroll_position), Some(position_map)) =
7681 (previous_scroll_position, self.last_position_map.as_ref())
7682 {
7683 self.set_scroll_position(
7684 previous_scroll_position
7685 .scroll_position(&position_map.snapshot.display_snapshot),
7686 window,
7687 cx,
7688 );
7689 }
7690
7691 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7692 released_too_fast: since.elapsed() < Duration::from_millis(200),
7693 };
7694 self.clear_row_highlights::<EditPredictionPreview>();
7695 self.update_visible_edit_prediction(window, cx);
7696 cx.notify();
7697 }
7698 }
7699
7700 fn update_visible_edit_prediction(
7701 &mut self,
7702 _window: &mut Window,
7703 cx: &mut Context<Self>,
7704 ) -> Option<()> {
7705 if DisableAiSettings::get_global(cx).disable_ai {
7706 return None;
7707 }
7708
7709 if self.ime_transaction.is_some() {
7710 self.discard_edit_prediction(false, cx);
7711 return None;
7712 }
7713
7714 let selection = self.selections.newest_anchor();
7715 let cursor = selection.head();
7716 let multibuffer = self.buffer.read(cx).snapshot(cx);
7717 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7718 let excerpt_id = cursor.excerpt_id;
7719
7720 let show_in_menu = self.show_edit_predictions_in_menu();
7721 let completions_menu_has_precedence = !show_in_menu
7722 && (self.context_menu.borrow().is_some()
7723 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7724
7725 if completions_menu_has_precedence
7726 || !offset_selection.is_empty()
7727 || self
7728 .active_edit_prediction
7729 .as_ref()
7730 .is_some_and(|completion| {
7731 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7732 return false;
7733 };
7734 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7735 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7736 !invalidation_range.contains(&offset_selection.head())
7737 })
7738 {
7739 self.discard_edit_prediction(false, cx);
7740 return None;
7741 }
7742
7743 self.take_active_edit_prediction(cx);
7744 let Some(provider) = self.edit_prediction_provider() else {
7745 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7746 return None;
7747 };
7748
7749 let (buffer, cursor_buffer_position) =
7750 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7751
7752 self.edit_prediction_settings =
7753 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7754
7755 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7756
7757 if self.edit_prediction_indent_conflict {
7758 let cursor_point = cursor.to_point(&multibuffer);
7759
7760 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7761
7762 if let Some((_, indent)) = indents.iter().next()
7763 && indent.len == cursor_point.column
7764 {
7765 self.edit_prediction_indent_conflict = false;
7766 }
7767 }
7768
7769 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7770
7771 let (completion_id, edits, edit_preview) = match edit_prediction {
7772 edit_prediction::EditPrediction::Local {
7773 id,
7774 edits,
7775 edit_preview,
7776 } => (id, edits, edit_preview),
7777 edit_prediction::EditPrediction::Jump {
7778 id,
7779 snapshot,
7780 target,
7781 } => {
7782 self.stale_edit_prediction_in_menu = None;
7783 self.active_edit_prediction = Some(EditPredictionState {
7784 inlay_ids: vec![],
7785 completion: EditPrediction::MoveOutside { snapshot, target },
7786 completion_id: id,
7787 invalidation_range: None,
7788 });
7789 cx.notify();
7790 return Some(());
7791 }
7792 };
7793
7794 let edits = edits
7795 .into_iter()
7796 .flat_map(|(range, new_text)| {
7797 Some((
7798 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7799 new_text,
7800 ))
7801 })
7802 .collect::<Vec<_>>();
7803 if edits.is_empty() {
7804 return None;
7805 }
7806
7807 let first_edit_start = edits.first().unwrap().0.start;
7808 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7809 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7810
7811 let last_edit_end = edits.last().unwrap().0.end;
7812 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7813 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7814
7815 let cursor_row = cursor.to_point(&multibuffer).row;
7816
7817 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7818
7819 let mut inlay_ids = Vec::new();
7820 let invalidation_row_range;
7821 let move_invalidation_row_range = if cursor_row < edit_start_row {
7822 Some(cursor_row..edit_end_row)
7823 } else if cursor_row > edit_end_row {
7824 Some(edit_start_row..cursor_row)
7825 } else {
7826 None
7827 };
7828 let supports_jump = self
7829 .edit_prediction_provider
7830 .as_ref()
7831 .map(|provider| provider.provider.supports_jump_to_edit())
7832 .unwrap_or(true);
7833
7834 let is_move = supports_jump
7835 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7836 let completion = if is_move {
7837 invalidation_row_range =
7838 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7839 let target = first_edit_start;
7840 EditPrediction::MoveWithin { target, snapshot }
7841 } else {
7842 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7843 && !self.edit_predictions_hidden_for_vim_mode;
7844
7845 if show_completions_in_buffer {
7846 if edits
7847 .iter()
7848 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7849 {
7850 let mut inlays = Vec::new();
7851 for (range, new_text) in &edits {
7852 let inlay = Inlay::edit_prediction(
7853 post_inc(&mut self.next_inlay_id),
7854 range.start,
7855 Rope::from_str_small(new_text.as_str()),
7856 );
7857 inlay_ids.push(inlay.id);
7858 inlays.push(inlay);
7859 }
7860
7861 self.splice_inlays(&[], inlays, cx);
7862 } else {
7863 let background_color = cx.theme().status().deleted_background;
7864 self.highlight_text::<EditPredictionHighlight>(
7865 edits.iter().map(|(range, _)| range.clone()).collect(),
7866 HighlightStyle {
7867 background_color: Some(background_color),
7868 ..Default::default()
7869 },
7870 cx,
7871 );
7872 }
7873 }
7874
7875 invalidation_row_range = edit_start_row..edit_end_row;
7876
7877 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7878 if provider.show_tab_accept_marker() {
7879 EditDisplayMode::TabAccept
7880 } else {
7881 EditDisplayMode::Inline
7882 }
7883 } else {
7884 EditDisplayMode::DiffPopover
7885 };
7886
7887 EditPrediction::Edit {
7888 edits,
7889 edit_preview,
7890 display_mode,
7891 snapshot,
7892 }
7893 };
7894
7895 let invalidation_range = multibuffer
7896 .anchor_before(Point::new(invalidation_row_range.start, 0))
7897 ..multibuffer.anchor_after(Point::new(
7898 invalidation_row_range.end,
7899 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7900 ));
7901
7902 self.stale_edit_prediction_in_menu = None;
7903 self.active_edit_prediction = Some(EditPredictionState {
7904 inlay_ids,
7905 completion,
7906 completion_id,
7907 invalidation_range: Some(invalidation_range),
7908 });
7909
7910 cx.notify();
7911
7912 Some(())
7913 }
7914
7915 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7916 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7917 }
7918
7919 fn clear_tasks(&mut self) {
7920 self.tasks.clear()
7921 }
7922
7923 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7924 if self.tasks.insert(key, value).is_some() {
7925 // This case should hopefully be rare, but just in case...
7926 log::error!(
7927 "multiple different run targets found on a single line, only the last target will be rendered"
7928 )
7929 }
7930 }
7931
7932 /// Get all display points of breakpoints that will be rendered within editor
7933 ///
7934 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7935 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7936 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7937 fn active_breakpoints(
7938 &self,
7939 range: Range<DisplayRow>,
7940 window: &mut Window,
7941 cx: &mut Context<Self>,
7942 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7943 let mut breakpoint_display_points = HashMap::default();
7944
7945 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7946 return breakpoint_display_points;
7947 };
7948
7949 let snapshot = self.snapshot(window, cx);
7950
7951 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7952 let Some(project) = self.project() else {
7953 return breakpoint_display_points;
7954 };
7955
7956 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7957 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7958
7959 for (buffer_snapshot, range, excerpt_id) in
7960 multi_buffer_snapshot.range_to_buffer_ranges(range)
7961 {
7962 let Some(buffer) = project
7963 .read(cx)
7964 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7965 else {
7966 continue;
7967 };
7968 let breakpoints = breakpoint_store.read(cx).breakpoints(
7969 &buffer,
7970 Some(
7971 buffer_snapshot.anchor_before(range.start)
7972 ..buffer_snapshot.anchor_after(range.end),
7973 ),
7974 buffer_snapshot,
7975 cx,
7976 );
7977 for (breakpoint, state) in breakpoints {
7978 let multi_buffer_anchor =
7979 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7980 let position = multi_buffer_anchor
7981 .to_point(&multi_buffer_snapshot)
7982 .to_display_point(&snapshot);
7983
7984 breakpoint_display_points.insert(
7985 position.row(),
7986 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7987 );
7988 }
7989 }
7990
7991 breakpoint_display_points
7992 }
7993
7994 fn breakpoint_context_menu(
7995 &self,
7996 anchor: Anchor,
7997 window: &mut Window,
7998 cx: &mut Context<Self>,
7999 ) -> Entity<ui::ContextMenu> {
8000 let weak_editor = cx.weak_entity();
8001 let focus_handle = self.focus_handle(cx);
8002
8003 let row = self
8004 .buffer
8005 .read(cx)
8006 .snapshot(cx)
8007 .summary_for_anchor::<Point>(&anchor)
8008 .row;
8009
8010 let breakpoint = self
8011 .breakpoint_at_row(row, window, cx)
8012 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8013
8014 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8015 "Edit Log Breakpoint"
8016 } else {
8017 "Set Log Breakpoint"
8018 };
8019
8020 let condition_breakpoint_msg = if breakpoint
8021 .as_ref()
8022 .is_some_and(|bp| bp.1.condition.is_some())
8023 {
8024 "Edit Condition Breakpoint"
8025 } else {
8026 "Set Condition Breakpoint"
8027 };
8028
8029 let hit_condition_breakpoint_msg = if breakpoint
8030 .as_ref()
8031 .is_some_and(|bp| bp.1.hit_condition.is_some())
8032 {
8033 "Edit Hit Condition Breakpoint"
8034 } else {
8035 "Set Hit Condition Breakpoint"
8036 };
8037
8038 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8039 "Unset Breakpoint"
8040 } else {
8041 "Set Breakpoint"
8042 };
8043
8044 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8045
8046 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8047 BreakpointState::Enabled => Some("Disable"),
8048 BreakpointState::Disabled => Some("Enable"),
8049 });
8050
8051 let (anchor, breakpoint) =
8052 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8053
8054 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8055 menu.on_blur_subscription(Subscription::new(|| {}))
8056 .context(focus_handle)
8057 .when(run_to_cursor, |this| {
8058 let weak_editor = weak_editor.clone();
8059 this.entry("Run to cursor", None, move |window, cx| {
8060 weak_editor
8061 .update(cx, |editor, cx| {
8062 editor.change_selections(
8063 SelectionEffects::no_scroll(),
8064 window,
8065 cx,
8066 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8067 );
8068 })
8069 .ok();
8070
8071 window.dispatch_action(Box::new(RunToCursor), cx);
8072 })
8073 .separator()
8074 })
8075 .when_some(toggle_state_msg, |this, msg| {
8076 this.entry(msg, None, {
8077 let weak_editor = weak_editor.clone();
8078 let breakpoint = breakpoint.clone();
8079 move |_window, cx| {
8080 weak_editor
8081 .update(cx, |this, cx| {
8082 this.edit_breakpoint_at_anchor(
8083 anchor,
8084 breakpoint.as_ref().clone(),
8085 BreakpointEditAction::InvertState,
8086 cx,
8087 );
8088 })
8089 .log_err();
8090 }
8091 })
8092 })
8093 .entry(set_breakpoint_msg, None, {
8094 let weak_editor = weak_editor.clone();
8095 let breakpoint = breakpoint.clone();
8096 move |_window, cx| {
8097 weak_editor
8098 .update(cx, |this, cx| {
8099 this.edit_breakpoint_at_anchor(
8100 anchor,
8101 breakpoint.as_ref().clone(),
8102 BreakpointEditAction::Toggle,
8103 cx,
8104 );
8105 })
8106 .log_err();
8107 }
8108 })
8109 .entry(log_breakpoint_msg, None, {
8110 let breakpoint = breakpoint.clone();
8111 let weak_editor = weak_editor.clone();
8112 move |window, cx| {
8113 weak_editor
8114 .update(cx, |this, cx| {
8115 this.add_edit_breakpoint_block(
8116 anchor,
8117 breakpoint.as_ref(),
8118 BreakpointPromptEditAction::Log,
8119 window,
8120 cx,
8121 );
8122 })
8123 .log_err();
8124 }
8125 })
8126 .entry(condition_breakpoint_msg, None, {
8127 let breakpoint = breakpoint.clone();
8128 let weak_editor = weak_editor.clone();
8129 move |window, cx| {
8130 weak_editor
8131 .update(cx, |this, cx| {
8132 this.add_edit_breakpoint_block(
8133 anchor,
8134 breakpoint.as_ref(),
8135 BreakpointPromptEditAction::Condition,
8136 window,
8137 cx,
8138 );
8139 })
8140 .log_err();
8141 }
8142 })
8143 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8144 weak_editor
8145 .update(cx, |this, cx| {
8146 this.add_edit_breakpoint_block(
8147 anchor,
8148 breakpoint.as_ref(),
8149 BreakpointPromptEditAction::HitCondition,
8150 window,
8151 cx,
8152 );
8153 })
8154 .log_err();
8155 })
8156 })
8157 }
8158
8159 fn render_breakpoint(
8160 &self,
8161 position: Anchor,
8162 row: DisplayRow,
8163 breakpoint: &Breakpoint,
8164 state: Option<BreakpointSessionState>,
8165 cx: &mut Context<Self>,
8166 ) -> IconButton {
8167 let is_rejected = state.is_some_and(|s| !s.verified);
8168 // Is it a breakpoint that shows up when hovering over gutter?
8169 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8170 (false, false),
8171 |PhantomBreakpointIndicator {
8172 is_active,
8173 display_row,
8174 collides_with_existing_breakpoint,
8175 }| {
8176 (
8177 is_active && display_row == row,
8178 collides_with_existing_breakpoint,
8179 )
8180 },
8181 );
8182
8183 let (color, icon) = {
8184 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8185 (false, false) => ui::IconName::DebugBreakpoint,
8186 (true, false) => ui::IconName::DebugLogBreakpoint,
8187 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8188 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8189 };
8190
8191 let color = if is_phantom {
8192 Color::Hint
8193 } else if is_rejected {
8194 Color::Disabled
8195 } else {
8196 Color::Debugger
8197 };
8198
8199 (color, icon)
8200 };
8201
8202 let breakpoint = Arc::from(breakpoint.clone());
8203
8204 let alt_as_text = gpui::Keystroke {
8205 modifiers: Modifiers::secondary_key(),
8206 ..Default::default()
8207 };
8208 let primary_action_text = if breakpoint.is_disabled() {
8209 "Enable breakpoint"
8210 } else if is_phantom && !collides_with_existing {
8211 "Set breakpoint"
8212 } else {
8213 "Unset breakpoint"
8214 };
8215 let focus_handle = self.focus_handle.clone();
8216
8217 let meta = if is_rejected {
8218 SharedString::from("No executable code is associated with this line.")
8219 } else if collides_with_existing && !breakpoint.is_disabled() {
8220 SharedString::from(format!(
8221 "{alt_as_text}-click to disable,\nright-click for more options."
8222 ))
8223 } else {
8224 SharedString::from("Right-click for more options.")
8225 };
8226 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8227 .icon_size(IconSize::XSmall)
8228 .size(ui::ButtonSize::None)
8229 .when(is_rejected, |this| {
8230 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8231 })
8232 .icon_color(color)
8233 .style(ButtonStyle::Transparent)
8234 .on_click(cx.listener({
8235 move |editor, event: &ClickEvent, window, cx| {
8236 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8237 BreakpointEditAction::InvertState
8238 } else {
8239 BreakpointEditAction::Toggle
8240 };
8241
8242 window.focus(&editor.focus_handle(cx));
8243 editor.edit_breakpoint_at_anchor(
8244 position,
8245 breakpoint.as_ref().clone(),
8246 edit_action,
8247 cx,
8248 );
8249 }
8250 }))
8251 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8252 editor.set_breakpoint_context_menu(
8253 row,
8254 Some(position),
8255 event.position(),
8256 window,
8257 cx,
8258 );
8259 }))
8260 .tooltip(move |_window, cx| {
8261 Tooltip::with_meta_in(
8262 primary_action_text,
8263 Some(&ToggleBreakpoint),
8264 meta.clone(),
8265 &focus_handle,
8266 cx,
8267 )
8268 })
8269 }
8270
8271 fn build_tasks_context(
8272 project: &Entity<Project>,
8273 buffer: &Entity<Buffer>,
8274 buffer_row: u32,
8275 tasks: &Arc<RunnableTasks>,
8276 cx: &mut Context<Self>,
8277 ) -> Task<Option<task::TaskContext>> {
8278 let position = Point::new(buffer_row, tasks.column);
8279 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8280 let location = Location {
8281 buffer: buffer.clone(),
8282 range: range_start..range_start,
8283 };
8284 // Fill in the environmental variables from the tree-sitter captures
8285 let mut captured_task_variables = TaskVariables::default();
8286 for (capture_name, value) in tasks.extra_variables.clone() {
8287 captured_task_variables.insert(
8288 task::VariableName::Custom(capture_name.into()),
8289 value.clone(),
8290 );
8291 }
8292 project.update(cx, |project, cx| {
8293 project.task_store().update(cx, |task_store, cx| {
8294 task_store.task_context_for_location(captured_task_variables, location, cx)
8295 })
8296 })
8297 }
8298
8299 pub fn spawn_nearest_task(
8300 &mut self,
8301 action: &SpawnNearestTask,
8302 window: &mut Window,
8303 cx: &mut Context<Self>,
8304 ) {
8305 let Some((workspace, _)) = self.workspace.clone() else {
8306 return;
8307 };
8308 let Some(project) = self.project.clone() else {
8309 return;
8310 };
8311
8312 // Try to find a closest, enclosing node using tree-sitter that has a task
8313 let Some((buffer, buffer_row, tasks)) = self
8314 .find_enclosing_node_task(cx)
8315 // Or find the task that's closest in row-distance.
8316 .or_else(|| self.find_closest_task(cx))
8317 else {
8318 return;
8319 };
8320
8321 let reveal_strategy = action.reveal;
8322 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8323 cx.spawn_in(window, async move |_, cx| {
8324 let context = task_context.await?;
8325 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8326
8327 let resolved = &mut resolved_task.resolved;
8328 resolved.reveal = reveal_strategy;
8329
8330 workspace
8331 .update_in(cx, |workspace, window, cx| {
8332 workspace.schedule_resolved_task(
8333 task_source_kind,
8334 resolved_task,
8335 false,
8336 window,
8337 cx,
8338 );
8339 })
8340 .ok()
8341 })
8342 .detach();
8343 }
8344
8345 fn find_closest_task(
8346 &mut self,
8347 cx: &mut Context<Self>,
8348 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8349 let cursor_row = self
8350 .selections
8351 .newest_adjusted(&self.display_snapshot(cx))
8352 .head()
8353 .row;
8354
8355 let ((buffer_id, row), tasks) = self
8356 .tasks
8357 .iter()
8358 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8359
8360 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8361 let tasks = Arc::new(tasks.to_owned());
8362 Some((buffer, *row, tasks))
8363 }
8364
8365 fn find_enclosing_node_task(
8366 &mut self,
8367 cx: &mut Context<Self>,
8368 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8369 let snapshot = self.buffer.read(cx).snapshot(cx);
8370 let offset = self
8371 .selections
8372 .newest::<usize>(&self.display_snapshot(cx))
8373 .head();
8374 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8375 let buffer_id = excerpt.buffer().remote_id();
8376
8377 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8378 let mut cursor = layer.node().walk();
8379
8380 while cursor.goto_first_child_for_byte(offset).is_some() {
8381 if cursor.node().end_byte() == offset {
8382 cursor.goto_next_sibling();
8383 }
8384 }
8385
8386 // Ascend to the smallest ancestor that contains the range and has a task.
8387 loop {
8388 let node = cursor.node();
8389 let node_range = node.byte_range();
8390 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8391
8392 // Check if this node contains our offset
8393 if node_range.start <= offset && node_range.end >= offset {
8394 // If it contains offset, check for task
8395 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8396 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8397 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8398 }
8399 }
8400
8401 if !cursor.goto_parent() {
8402 break;
8403 }
8404 }
8405 None
8406 }
8407
8408 fn render_run_indicator(
8409 &self,
8410 _style: &EditorStyle,
8411 is_active: bool,
8412 row: DisplayRow,
8413 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8414 cx: &mut Context<Self>,
8415 ) -> IconButton {
8416 let color = Color::Muted;
8417 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8418
8419 IconButton::new(
8420 ("run_indicator", row.0 as usize),
8421 ui::IconName::PlayOutlined,
8422 )
8423 .shape(ui::IconButtonShape::Square)
8424 .icon_size(IconSize::XSmall)
8425 .icon_color(color)
8426 .toggle_state(is_active)
8427 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8428 let quick_launch = match e {
8429 ClickEvent::Keyboard(_) => true,
8430 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8431 };
8432
8433 window.focus(&editor.focus_handle(cx));
8434 editor.toggle_code_actions(
8435 &ToggleCodeActions {
8436 deployed_from: Some(CodeActionSource::RunMenu(row)),
8437 quick_launch,
8438 },
8439 window,
8440 cx,
8441 );
8442 }))
8443 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8444 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8445 }))
8446 }
8447
8448 pub fn context_menu_visible(&self) -> bool {
8449 !self.edit_prediction_preview_is_active()
8450 && self
8451 .context_menu
8452 .borrow()
8453 .as_ref()
8454 .is_some_and(|menu| menu.visible())
8455 }
8456
8457 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8458 self.context_menu
8459 .borrow()
8460 .as_ref()
8461 .map(|menu| menu.origin())
8462 }
8463
8464 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8465 self.context_menu_options = Some(options);
8466 }
8467
8468 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8469 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8470
8471 fn render_edit_prediction_popover(
8472 &mut self,
8473 text_bounds: &Bounds<Pixels>,
8474 content_origin: gpui::Point<Pixels>,
8475 right_margin: Pixels,
8476 editor_snapshot: &EditorSnapshot,
8477 visible_row_range: Range<DisplayRow>,
8478 scroll_top: ScrollOffset,
8479 scroll_bottom: ScrollOffset,
8480 line_layouts: &[LineWithInvisibles],
8481 line_height: Pixels,
8482 scroll_position: gpui::Point<ScrollOffset>,
8483 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8484 newest_selection_head: Option<DisplayPoint>,
8485 editor_width: Pixels,
8486 style: &EditorStyle,
8487 window: &mut Window,
8488 cx: &mut App,
8489 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8490 if self.mode().is_minimap() {
8491 return None;
8492 }
8493 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8494
8495 if self.edit_prediction_visible_in_cursor_popover(true) {
8496 return None;
8497 }
8498
8499 match &active_edit_prediction.completion {
8500 EditPrediction::MoveWithin { target, .. } => {
8501 let target_display_point = target.to_display_point(editor_snapshot);
8502
8503 if self.edit_prediction_requires_modifier() {
8504 if !self.edit_prediction_preview_is_active() {
8505 return None;
8506 }
8507
8508 self.render_edit_prediction_modifier_jump_popover(
8509 text_bounds,
8510 content_origin,
8511 visible_row_range,
8512 line_layouts,
8513 line_height,
8514 scroll_pixel_position,
8515 newest_selection_head,
8516 target_display_point,
8517 window,
8518 cx,
8519 )
8520 } else {
8521 self.render_edit_prediction_eager_jump_popover(
8522 text_bounds,
8523 content_origin,
8524 editor_snapshot,
8525 visible_row_range,
8526 scroll_top,
8527 scroll_bottom,
8528 line_height,
8529 scroll_pixel_position,
8530 target_display_point,
8531 editor_width,
8532 window,
8533 cx,
8534 )
8535 }
8536 }
8537 EditPrediction::Edit {
8538 display_mode: EditDisplayMode::Inline,
8539 ..
8540 } => None,
8541 EditPrediction::Edit {
8542 display_mode: EditDisplayMode::TabAccept,
8543 edits,
8544 ..
8545 } => {
8546 let range = &edits.first()?.0;
8547 let target_display_point = range.end.to_display_point(editor_snapshot);
8548
8549 self.render_edit_prediction_end_of_line_popover(
8550 "Accept",
8551 editor_snapshot,
8552 visible_row_range,
8553 target_display_point,
8554 line_height,
8555 scroll_pixel_position,
8556 content_origin,
8557 editor_width,
8558 window,
8559 cx,
8560 )
8561 }
8562 EditPrediction::Edit {
8563 edits,
8564 edit_preview,
8565 display_mode: EditDisplayMode::DiffPopover,
8566 snapshot,
8567 } => self.render_edit_prediction_diff_popover(
8568 text_bounds,
8569 content_origin,
8570 right_margin,
8571 editor_snapshot,
8572 visible_row_range,
8573 line_layouts,
8574 line_height,
8575 scroll_position,
8576 scroll_pixel_position,
8577 newest_selection_head,
8578 editor_width,
8579 style,
8580 edits,
8581 edit_preview,
8582 snapshot,
8583 window,
8584 cx,
8585 ),
8586 EditPrediction::MoveOutside { snapshot, .. } => {
8587 let file_name = snapshot
8588 .file()
8589 .map(|file| file.file_name(cx))
8590 .unwrap_or("untitled");
8591 let mut element = self
8592 .render_edit_prediction_line_popover(
8593 format!("Jump to {file_name}"),
8594 Some(IconName::ZedPredict),
8595 window,
8596 cx,
8597 )
8598 .into_any();
8599
8600 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8601 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8602 let origin_y = text_bounds.size.height - size.height - px(30.);
8603 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8604 element.prepaint_at(origin, window, cx);
8605
8606 Some((element, origin))
8607 }
8608 }
8609 }
8610
8611 fn render_edit_prediction_modifier_jump_popover(
8612 &mut self,
8613 text_bounds: &Bounds<Pixels>,
8614 content_origin: gpui::Point<Pixels>,
8615 visible_row_range: Range<DisplayRow>,
8616 line_layouts: &[LineWithInvisibles],
8617 line_height: Pixels,
8618 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8619 newest_selection_head: Option<DisplayPoint>,
8620 target_display_point: DisplayPoint,
8621 window: &mut Window,
8622 cx: &mut App,
8623 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8624 let scrolled_content_origin =
8625 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8626
8627 const SCROLL_PADDING_Y: Pixels = px(12.);
8628
8629 if target_display_point.row() < visible_row_range.start {
8630 return self.render_edit_prediction_scroll_popover(
8631 |_| SCROLL_PADDING_Y,
8632 IconName::ArrowUp,
8633 visible_row_range,
8634 line_layouts,
8635 newest_selection_head,
8636 scrolled_content_origin,
8637 window,
8638 cx,
8639 );
8640 } else if target_display_point.row() >= visible_row_range.end {
8641 return self.render_edit_prediction_scroll_popover(
8642 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8643 IconName::ArrowDown,
8644 visible_row_range,
8645 line_layouts,
8646 newest_selection_head,
8647 scrolled_content_origin,
8648 window,
8649 cx,
8650 );
8651 }
8652
8653 const POLE_WIDTH: Pixels = px(2.);
8654
8655 let line_layout =
8656 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8657 let target_column = target_display_point.column() as usize;
8658
8659 let target_x = line_layout.x_for_index(target_column);
8660 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8661 - scroll_pixel_position.y;
8662
8663 let flag_on_right = target_x < text_bounds.size.width / 2.;
8664
8665 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8666 border_color.l += 0.001;
8667
8668 let mut element = v_flex()
8669 .items_end()
8670 .when(flag_on_right, |el| el.items_start())
8671 .child(if flag_on_right {
8672 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8673 .rounded_bl(px(0.))
8674 .rounded_tl(px(0.))
8675 .border_l_2()
8676 .border_color(border_color)
8677 } else {
8678 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8679 .rounded_br(px(0.))
8680 .rounded_tr(px(0.))
8681 .border_r_2()
8682 .border_color(border_color)
8683 })
8684 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8685 .into_any();
8686
8687 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8688
8689 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8690 - point(
8691 if flag_on_right {
8692 POLE_WIDTH
8693 } else {
8694 size.width - POLE_WIDTH
8695 },
8696 size.height - line_height,
8697 );
8698
8699 origin.x = origin.x.max(content_origin.x);
8700
8701 element.prepaint_at(origin, window, cx);
8702
8703 Some((element, origin))
8704 }
8705
8706 fn render_edit_prediction_scroll_popover(
8707 &mut self,
8708 to_y: impl Fn(Size<Pixels>) -> Pixels,
8709 scroll_icon: IconName,
8710 visible_row_range: Range<DisplayRow>,
8711 line_layouts: &[LineWithInvisibles],
8712 newest_selection_head: Option<DisplayPoint>,
8713 scrolled_content_origin: gpui::Point<Pixels>,
8714 window: &mut Window,
8715 cx: &mut App,
8716 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8717 let mut element = self
8718 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8719 .into_any();
8720
8721 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8722
8723 let cursor = newest_selection_head?;
8724 let cursor_row_layout =
8725 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8726 let cursor_column = cursor.column() as usize;
8727
8728 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8729
8730 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8731
8732 element.prepaint_at(origin, window, cx);
8733 Some((element, origin))
8734 }
8735
8736 fn render_edit_prediction_eager_jump_popover(
8737 &mut self,
8738 text_bounds: &Bounds<Pixels>,
8739 content_origin: gpui::Point<Pixels>,
8740 editor_snapshot: &EditorSnapshot,
8741 visible_row_range: Range<DisplayRow>,
8742 scroll_top: ScrollOffset,
8743 scroll_bottom: ScrollOffset,
8744 line_height: Pixels,
8745 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8746 target_display_point: DisplayPoint,
8747 editor_width: Pixels,
8748 window: &mut Window,
8749 cx: &mut App,
8750 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8751 if target_display_point.row().as_f64() < scroll_top {
8752 let mut element = self
8753 .render_edit_prediction_line_popover(
8754 "Jump to Edit",
8755 Some(IconName::ArrowUp),
8756 window,
8757 cx,
8758 )
8759 .into_any();
8760
8761 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8762 let offset = point(
8763 (text_bounds.size.width - size.width) / 2.,
8764 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8765 );
8766
8767 let origin = text_bounds.origin + offset;
8768 element.prepaint_at(origin, window, cx);
8769 Some((element, origin))
8770 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8771 let mut element = self
8772 .render_edit_prediction_line_popover(
8773 "Jump to Edit",
8774 Some(IconName::ArrowDown),
8775 window,
8776 cx,
8777 )
8778 .into_any();
8779
8780 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8781 let offset = point(
8782 (text_bounds.size.width - size.width) / 2.,
8783 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8784 );
8785
8786 let origin = text_bounds.origin + offset;
8787 element.prepaint_at(origin, window, cx);
8788 Some((element, origin))
8789 } else {
8790 self.render_edit_prediction_end_of_line_popover(
8791 "Jump to Edit",
8792 editor_snapshot,
8793 visible_row_range,
8794 target_display_point,
8795 line_height,
8796 scroll_pixel_position,
8797 content_origin,
8798 editor_width,
8799 window,
8800 cx,
8801 )
8802 }
8803 }
8804
8805 fn render_edit_prediction_end_of_line_popover(
8806 self: &mut Editor,
8807 label: &'static str,
8808 editor_snapshot: &EditorSnapshot,
8809 visible_row_range: Range<DisplayRow>,
8810 target_display_point: DisplayPoint,
8811 line_height: Pixels,
8812 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8813 content_origin: gpui::Point<Pixels>,
8814 editor_width: Pixels,
8815 window: &mut Window,
8816 cx: &mut App,
8817 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8818 let target_line_end = DisplayPoint::new(
8819 target_display_point.row(),
8820 editor_snapshot.line_len(target_display_point.row()),
8821 );
8822
8823 let mut element = self
8824 .render_edit_prediction_line_popover(label, None, window, cx)
8825 .into_any();
8826
8827 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8828
8829 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8830
8831 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8832 let mut origin = start_point
8833 + line_origin
8834 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8835 origin.x = origin.x.max(content_origin.x);
8836
8837 let max_x = content_origin.x + editor_width - size.width;
8838
8839 if origin.x > max_x {
8840 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8841
8842 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8843 origin.y += offset;
8844 IconName::ArrowUp
8845 } else {
8846 origin.y -= offset;
8847 IconName::ArrowDown
8848 };
8849
8850 element = self
8851 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8852 .into_any();
8853
8854 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8855
8856 origin.x = content_origin.x + editor_width - size.width - px(2.);
8857 }
8858
8859 element.prepaint_at(origin, window, cx);
8860 Some((element, origin))
8861 }
8862
8863 fn render_edit_prediction_diff_popover(
8864 self: &Editor,
8865 text_bounds: &Bounds<Pixels>,
8866 content_origin: gpui::Point<Pixels>,
8867 right_margin: Pixels,
8868 editor_snapshot: &EditorSnapshot,
8869 visible_row_range: Range<DisplayRow>,
8870 line_layouts: &[LineWithInvisibles],
8871 line_height: Pixels,
8872 scroll_position: gpui::Point<ScrollOffset>,
8873 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8874 newest_selection_head: Option<DisplayPoint>,
8875 editor_width: Pixels,
8876 style: &EditorStyle,
8877 edits: &Vec<(Range<Anchor>, String)>,
8878 edit_preview: &Option<language::EditPreview>,
8879 snapshot: &language::BufferSnapshot,
8880 window: &mut Window,
8881 cx: &mut App,
8882 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8883 let edit_start = edits
8884 .first()
8885 .unwrap()
8886 .0
8887 .start
8888 .to_display_point(editor_snapshot);
8889 let edit_end = edits
8890 .last()
8891 .unwrap()
8892 .0
8893 .end
8894 .to_display_point(editor_snapshot);
8895
8896 let is_visible = visible_row_range.contains(&edit_start.row())
8897 || visible_row_range.contains(&edit_end.row());
8898 if !is_visible {
8899 return None;
8900 }
8901
8902 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8903 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8904 } else {
8905 // Fallback for providers without edit_preview
8906 crate::edit_prediction_fallback_text(edits, cx)
8907 };
8908
8909 let styled_text = highlighted_edits.to_styled_text(&style.text);
8910 let line_count = highlighted_edits.text.lines().count();
8911
8912 const BORDER_WIDTH: Pixels = px(1.);
8913
8914 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8915 let has_keybind = keybind.is_some();
8916
8917 let mut element = h_flex()
8918 .items_start()
8919 .child(
8920 h_flex()
8921 .bg(cx.theme().colors().editor_background)
8922 .border(BORDER_WIDTH)
8923 .shadow_xs()
8924 .border_color(cx.theme().colors().border)
8925 .rounded_l_lg()
8926 .when(line_count > 1, |el| el.rounded_br_lg())
8927 .pr_1()
8928 .child(styled_text),
8929 )
8930 .child(
8931 h_flex()
8932 .h(line_height + BORDER_WIDTH * 2.)
8933 .px_1p5()
8934 .gap_1()
8935 // Workaround: For some reason, there's a gap if we don't do this
8936 .ml(-BORDER_WIDTH)
8937 .shadow(vec![gpui::BoxShadow {
8938 color: gpui::black().opacity(0.05),
8939 offset: point(px(1.), px(1.)),
8940 blur_radius: px(2.),
8941 spread_radius: px(0.),
8942 }])
8943 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8944 .border(BORDER_WIDTH)
8945 .border_color(cx.theme().colors().border)
8946 .rounded_r_lg()
8947 .id("edit_prediction_diff_popover_keybind")
8948 .when(!has_keybind, |el| {
8949 let status_colors = cx.theme().status();
8950
8951 el.bg(status_colors.error_background)
8952 .border_color(status_colors.error.opacity(0.6))
8953 .child(Icon::new(IconName::Info).color(Color::Error))
8954 .cursor_default()
8955 .hoverable_tooltip(move |_window, cx| {
8956 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8957 })
8958 })
8959 .children(keybind),
8960 )
8961 .into_any();
8962
8963 let longest_row =
8964 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8965 let longest_line_width = if visible_row_range.contains(&longest_row) {
8966 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8967 } else {
8968 layout_line(
8969 longest_row,
8970 editor_snapshot,
8971 style,
8972 editor_width,
8973 |_| false,
8974 window,
8975 cx,
8976 )
8977 .width
8978 };
8979
8980 let viewport_bounds =
8981 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8982 right: -right_margin,
8983 ..Default::default()
8984 });
8985
8986 let x_after_longest = Pixels::from(
8987 ScrollPixelOffset::from(
8988 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
8989 ) - scroll_pixel_position.x,
8990 );
8991
8992 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8993
8994 // Fully visible if it can be displayed within the window (allow overlapping other
8995 // panes). However, this is only allowed if the popover starts within text_bounds.
8996 let can_position_to_the_right = x_after_longest < text_bounds.right()
8997 && x_after_longest + element_bounds.width < viewport_bounds.right();
8998
8999 let mut origin = if can_position_to_the_right {
9000 point(
9001 x_after_longest,
9002 text_bounds.origin.y
9003 + Pixels::from(
9004 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9005 - scroll_pixel_position.y,
9006 ),
9007 )
9008 } else {
9009 let cursor_row = newest_selection_head.map(|head| head.row());
9010 let above_edit = edit_start
9011 .row()
9012 .0
9013 .checked_sub(line_count as u32)
9014 .map(DisplayRow);
9015 let below_edit = Some(edit_end.row() + 1);
9016 let above_cursor =
9017 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9018 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9019
9020 // Place the edit popover adjacent to the edit if there is a location
9021 // available that is onscreen and does not obscure the cursor. Otherwise,
9022 // place it adjacent to the cursor.
9023 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9024 .into_iter()
9025 .flatten()
9026 .find(|&start_row| {
9027 let end_row = start_row + line_count as u32;
9028 visible_row_range.contains(&start_row)
9029 && visible_row_range.contains(&end_row)
9030 && cursor_row
9031 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9032 })?;
9033
9034 content_origin
9035 + point(
9036 Pixels::from(-scroll_pixel_position.x),
9037 Pixels::from(
9038 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9039 ),
9040 )
9041 };
9042
9043 origin.x -= BORDER_WIDTH;
9044
9045 window.defer_draw(element, origin, 1);
9046
9047 // Do not return an element, since it will already be drawn due to defer_draw.
9048 None
9049 }
9050
9051 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9052 px(30.)
9053 }
9054
9055 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9056 if self.read_only(cx) {
9057 cx.theme().players().read_only()
9058 } else {
9059 self.style.as_ref().unwrap().local_player
9060 }
9061 }
9062
9063 fn render_edit_prediction_accept_keybind(
9064 &self,
9065 window: &mut Window,
9066 cx: &mut App,
9067 ) -> Option<AnyElement> {
9068 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9069 let accept_keystroke = accept_binding.keystroke()?;
9070
9071 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9072
9073 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9074 Color::Accent
9075 } else {
9076 Color::Muted
9077 };
9078
9079 h_flex()
9080 .px_0p5()
9081 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9082 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9083 .text_size(TextSize::XSmall.rems(cx))
9084 .child(h_flex().children(ui::render_modifiers(
9085 accept_keystroke.modifiers(),
9086 PlatformStyle::platform(),
9087 Some(modifiers_color),
9088 Some(IconSize::XSmall.rems().into()),
9089 true,
9090 )))
9091 .when(is_platform_style_mac, |parent| {
9092 parent.child(accept_keystroke.key().to_string())
9093 })
9094 .when(!is_platform_style_mac, |parent| {
9095 parent.child(
9096 Key::new(
9097 util::capitalize(accept_keystroke.key()),
9098 Some(Color::Default),
9099 )
9100 .size(Some(IconSize::XSmall.rems().into())),
9101 )
9102 })
9103 .into_any()
9104 .into()
9105 }
9106
9107 fn render_edit_prediction_line_popover(
9108 &self,
9109 label: impl Into<SharedString>,
9110 icon: Option<IconName>,
9111 window: &mut Window,
9112 cx: &mut App,
9113 ) -> Stateful<Div> {
9114 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9115
9116 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9117 let has_keybind = keybind.is_some();
9118
9119 h_flex()
9120 .id("ep-line-popover")
9121 .py_0p5()
9122 .pl_1()
9123 .pr(padding_right)
9124 .gap_1()
9125 .rounded_md()
9126 .border_1()
9127 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9128 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9129 .shadow_xs()
9130 .when(!has_keybind, |el| {
9131 let status_colors = cx.theme().status();
9132
9133 el.bg(status_colors.error_background)
9134 .border_color(status_colors.error.opacity(0.6))
9135 .pl_2()
9136 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9137 .cursor_default()
9138 .hoverable_tooltip(move |_window, cx| {
9139 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9140 })
9141 })
9142 .children(keybind)
9143 .child(
9144 Label::new(label)
9145 .size(LabelSize::Small)
9146 .when(!has_keybind, |el| {
9147 el.color(cx.theme().status().error.into()).strikethrough()
9148 }),
9149 )
9150 .when(!has_keybind, |el| {
9151 el.child(
9152 h_flex().ml_1().child(
9153 Icon::new(IconName::Info)
9154 .size(IconSize::Small)
9155 .color(cx.theme().status().error.into()),
9156 ),
9157 )
9158 })
9159 .when_some(icon, |element, icon| {
9160 element.child(
9161 div()
9162 .mt(px(1.5))
9163 .child(Icon::new(icon).size(IconSize::Small)),
9164 )
9165 })
9166 }
9167
9168 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9169 let accent_color = cx.theme().colors().text_accent;
9170 let editor_bg_color = cx.theme().colors().editor_background;
9171 editor_bg_color.blend(accent_color.opacity(0.1))
9172 }
9173
9174 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9175 let accent_color = cx.theme().colors().text_accent;
9176 let editor_bg_color = cx.theme().colors().editor_background;
9177 editor_bg_color.blend(accent_color.opacity(0.6))
9178 }
9179 fn get_prediction_provider_icon_name(
9180 provider: &Option<RegisteredEditPredictionProvider>,
9181 ) -> IconName {
9182 match provider {
9183 Some(provider) => match provider.provider.name() {
9184 "copilot" => IconName::Copilot,
9185 "supermaven" => IconName::Supermaven,
9186 _ => IconName::ZedPredict,
9187 },
9188 None => IconName::ZedPredict,
9189 }
9190 }
9191
9192 fn render_edit_prediction_cursor_popover(
9193 &self,
9194 min_width: Pixels,
9195 max_width: Pixels,
9196 cursor_point: Point,
9197 style: &EditorStyle,
9198 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9199 _window: &Window,
9200 cx: &mut Context<Editor>,
9201 ) -> Option<AnyElement> {
9202 let provider = self.edit_prediction_provider.as_ref()?;
9203 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9204
9205 let is_refreshing = provider.provider.is_refreshing(cx);
9206
9207 fn pending_completion_container(icon: IconName) -> Div {
9208 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9209 }
9210
9211 let completion = match &self.active_edit_prediction {
9212 Some(prediction) => {
9213 if !self.has_visible_completions_menu() {
9214 const RADIUS: Pixels = px(6.);
9215 const BORDER_WIDTH: Pixels = px(1.);
9216
9217 return Some(
9218 h_flex()
9219 .elevation_2(cx)
9220 .border(BORDER_WIDTH)
9221 .border_color(cx.theme().colors().border)
9222 .when(accept_keystroke.is_none(), |el| {
9223 el.border_color(cx.theme().status().error)
9224 })
9225 .rounded(RADIUS)
9226 .rounded_tl(px(0.))
9227 .overflow_hidden()
9228 .child(div().px_1p5().child(match &prediction.completion {
9229 EditPrediction::MoveWithin { target, snapshot } => {
9230 use text::ToPoint as _;
9231 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9232 {
9233 Icon::new(IconName::ZedPredictDown)
9234 } else {
9235 Icon::new(IconName::ZedPredictUp)
9236 }
9237 }
9238 EditPrediction::MoveOutside { .. } => {
9239 // TODO [zeta2] custom icon for external jump?
9240 Icon::new(provider_icon)
9241 }
9242 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9243 }))
9244 .child(
9245 h_flex()
9246 .gap_1()
9247 .py_1()
9248 .px_2()
9249 .rounded_r(RADIUS - BORDER_WIDTH)
9250 .border_l_1()
9251 .border_color(cx.theme().colors().border)
9252 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9253 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9254 el.child(
9255 Label::new("Hold")
9256 .size(LabelSize::Small)
9257 .when(accept_keystroke.is_none(), |el| {
9258 el.strikethrough()
9259 })
9260 .line_height_style(LineHeightStyle::UiLabel),
9261 )
9262 })
9263 .id("edit_prediction_cursor_popover_keybind")
9264 .when(accept_keystroke.is_none(), |el| {
9265 let status_colors = cx.theme().status();
9266
9267 el.bg(status_colors.error_background)
9268 .border_color(status_colors.error.opacity(0.6))
9269 .child(Icon::new(IconName::Info).color(Color::Error))
9270 .cursor_default()
9271 .hoverable_tooltip(move |_window, cx| {
9272 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9273 .into()
9274 })
9275 })
9276 .when_some(
9277 accept_keystroke.as_ref(),
9278 |el, accept_keystroke| {
9279 el.child(h_flex().children(ui::render_modifiers(
9280 accept_keystroke.modifiers(),
9281 PlatformStyle::platform(),
9282 Some(Color::Default),
9283 Some(IconSize::XSmall.rems().into()),
9284 false,
9285 )))
9286 },
9287 ),
9288 )
9289 .into_any(),
9290 );
9291 }
9292
9293 self.render_edit_prediction_cursor_popover_preview(
9294 prediction,
9295 cursor_point,
9296 style,
9297 cx,
9298 )?
9299 }
9300
9301 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9302 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9303 stale_completion,
9304 cursor_point,
9305 style,
9306 cx,
9307 )?,
9308
9309 None => pending_completion_container(provider_icon)
9310 .child(Label::new("...").size(LabelSize::Small)),
9311 },
9312
9313 None => pending_completion_container(provider_icon)
9314 .child(Label::new("...").size(LabelSize::Small)),
9315 };
9316
9317 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9318 completion
9319 .with_animation(
9320 "loading-completion",
9321 Animation::new(Duration::from_secs(2))
9322 .repeat()
9323 .with_easing(pulsating_between(0.4, 0.8)),
9324 |label, delta| label.opacity(delta),
9325 )
9326 .into_any_element()
9327 } else {
9328 completion.into_any_element()
9329 };
9330
9331 let has_completion = self.active_edit_prediction.is_some();
9332
9333 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9334 Some(
9335 h_flex()
9336 .min_w(min_width)
9337 .max_w(max_width)
9338 .flex_1()
9339 .elevation_2(cx)
9340 .border_color(cx.theme().colors().border)
9341 .child(
9342 div()
9343 .flex_1()
9344 .py_1()
9345 .px_2()
9346 .overflow_hidden()
9347 .child(completion),
9348 )
9349 .when_some(accept_keystroke, |el, accept_keystroke| {
9350 if !accept_keystroke.modifiers().modified() {
9351 return el;
9352 }
9353
9354 el.child(
9355 h_flex()
9356 .h_full()
9357 .border_l_1()
9358 .rounded_r_lg()
9359 .border_color(cx.theme().colors().border)
9360 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9361 .gap_1()
9362 .py_1()
9363 .px_2()
9364 .child(
9365 h_flex()
9366 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9367 .when(is_platform_style_mac, |parent| parent.gap_1())
9368 .child(h_flex().children(ui::render_modifiers(
9369 accept_keystroke.modifiers(),
9370 PlatformStyle::platform(),
9371 Some(if !has_completion {
9372 Color::Muted
9373 } else {
9374 Color::Default
9375 }),
9376 None,
9377 false,
9378 ))),
9379 )
9380 .child(Label::new("Preview").into_any_element())
9381 .opacity(if has_completion { 1.0 } else { 0.4 }),
9382 )
9383 })
9384 .into_any(),
9385 )
9386 }
9387
9388 fn render_edit_prediction_cursor_popover_preview(
9389 &self,
9390 completion: &EditPredictionState,
9391 cursor_point: Point,
9392 style: &EditorStyle,
9393 cx: &mut Context<Editor>,
9394 ) -> Option<Div> {
9395 use text::ToPoint as _;
9396
9397 fn render_relative_row_jump(
9398 prefix: impl Into<String>,
9399 current_row: u32,
9400 target_row: u32,
9401 ) -> Div {
9402 let (row_diff, arrow) = if target_row < current_row {
9403 (current_row - target_row, IconName::ArrowUp)
9404 } else {
9405 (target_row - current_row, IconName::ArrowDown)
9406 };
9407
9408 h_flex()
9409 .child(
9410 Label::new(format!("{}{}", prefix.into(), row_diff))
9411 .color(Color::Muted)
9412 .size(LabelSize::Small),
9413 )
9414 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9415 }
9416
9417 let supports_jump = self
9418 .edit_prediction_provider
9419 .as_ref()
9420 .map(|provider| provider.provider.supports_jump_to_edit())
9421 .unwrap_or(true);
9422
9423 match &completion.completion {
9424 EditPrediction::MoveWithin {
9425 target, snapshot, ..
9426 } => {
9427 if !supports_jump {
9428 return None;
9429 }
9430
9431 Some(
9432 h_flex()
9433 .px_2()
9434 .gap_2()
9435 .flex_1()
9436 .child(
9437 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9438 Icon::new(IconName::ZedPredictDown)
9439 } else {
9440 Icon::new(IconName::ZedPredictUp)
9441 },
9442 )
9443 .child(Label::new("Jump to Edit")),
9444 )
9445 }
9446 EditPrediction::MoveOutside { snapshot, .. } => {
9447 let file_name = snapshot
9448 .file()
9449 .map(|file| file.file_name(cx))
9450 .unwrap_or("untitled");
9451 Some(
9452 h_flex()
9453 .px_2()
9454 .gap_2()
9455 .flex_1()
9456 .child(Icon::new(IconName::ZedPredict))
9457 .child(Label::new(format!("Jump to {file_name}"))),
9458 )
9459 }
9460 EditPrediction::Edit {
9461 edits,
9462 edit_preview,
9463 snapshot,
9464 display_mode: _,
9465 } => {
9466 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9467
9468 let (highlighted_edits, has_more_lines) =
9469 if let Some(edit_preview) = edit_preview.as_ref() {
9470 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9471 .first_line_preview()
9472 } else {
9473 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9474 };
9475
9476 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9477 .with_default_highlights(&style.text, highlighted_edits.highlights);
9478
9479 let preview = h_flex()
9480 .gap_1()
9481 .min_w_16()
9482 .child(styled_text)
9483 .when(has_more_lines, |parent| parent.child("…"));
9484
9485 let left = if supports_jump && first_edit_row != cursor_point.row {
9486 render_relative_row_jump("", cursor_point.row, first_edit_row)
9487 .into_any_element()
9488 } else {
9489 let icon_name =
9490 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9491 Icon::new(icon_name).into_any_element()
9492 };
9493
9494 Some(
9495 h_flex()
9496 .h_full()
9497 .flex_1()
9498 .gap_2()
9499 .pr_1()
9500 .overflow_x_hidden()
9501 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9502 .child(left)
9503 .child(preview),
9504 )
9505 }
9506 }
9507 }
9508
9509 pub fn render_context_menu(
9510 &self,
9511 style: &EditorStyle,
9512 max_height_in_lines: u32,
9513 window: &mut Window,
9514 cx: &mut Context<Editor>,
9515 ) -> Option<AnyElement> {
9516 let menu = self.context_menu.borrow();
9517 let menu = menu.as_ref()?;
9518 if !menu.visible() {
9519 return None;
9520 };
9521 Some(menu.render(style, max_height_in_lines, window, cx))
9522 }
9523
9524 fn render_context_menu_aside(
9525 &mut self,
9526 max_size: Size<Pixels>,
9527 window: &mut Window,
9528 cx: &mut Context<Editor>,
9529 ) -> Option<AnyElement> {
9530 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9531 if menu.visible() {
9532 menu.render_aside(max_size, window, cx)
9533 } else {
9534 None
9535 }
9536 })
9537 }
9538
9539 fn hide_context_menu(
9540 &mut self,
9541 window: &mut Window,
9542 cx: &mut Context<Self>,
9543 ) -> Option<CodeContextMenu> {
9544 cx.notify();
9545 self.completion_tasks.clear();
9546 let context_menu = self.context_menu.borrow_mut().take();
9547 self.stale_edit_prediction_in_menu.take();
9548 self.update_visible_edit_prediction(window, cx);
9549 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9550 && let Some(completion_provider) = &self.completion_provider
9551 {
9552 completion_provider.selection_changed(None, window, cx);
9553 }
9554 context_menu
9555 }
9556
9557 fn show_snippet_choices(
9558 &mut self,
9559 choices: &Vec<String>,
9560 selection: Range<Anchor>,
9561 cx: &mut Context<Self>,
9562 ) {
9563 let Some((_, buffer, _)) = self
9564 .buffer()
9565 .read(cx)
9566 .excerpt_containing(selection.start, cx)
9567 else {
9568 return;
9569 };
9570 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9571 else {
9572 return;
9573 };
9574 if buffer != end_buffer {
9575 log::error!("expected anchor range to have matching buffer IDs");
9576 return;
9577 }
9578
9579 let id = post_inc(&mut self.next_completion_id);
9580 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9581 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9582 CompletionsMenu::new_snippet_choices(
9583 id,
9584 true,
9585 choices,
9586 selection,
9587 buffer,
9588 snippet_sort_order,
9589 ),
9590 ));
9591 }
9592
9593 pub fn insert_snippet(
9594 &mut self,
9595 insertion_ranges: &[Range<usize>],
9596 snippet: Snippet,
9597 window: &mut Window,
9598 cx: &mut Context<Self>,
9599 ) -> Result<()> {
9600 struct Tabstop<T> {
9601 is_end_tabstop: bool,
9602 ranges: Vec<Range<T>>,
9603 choices: Option<Vec<String>>,
9604 }
9605
9606 let tabstops = self.buffer.update(cx, |buffer, cx| {
9607 let snippet_text: Arc<str> = snippet.text.clone().into();
9608 let edits = insertion_ranges
9609 .iter()
9610 .cloned()
9611 .map(|range| (range, snippet_text.clone()));
9612 let autoindent_mode = AutoindentMode::Block {
9613 original_indent_columns: Vec::new(),
9614 };
9615 buffer.edit(edits, Some(autoindent_mode), cx);
9616
9617 let snapshot = &*buffer.read(cx);
9618 let snippet = &snippet;
9619 snippet
9620 .tabstops
9621 .iter()
9622 .map(|tabstop| {
9623 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9624 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9625 });
9626 let mut tabstop_ranges = tabstop
9627 .ranges
9628 .iter()
9629 .flat_map(|tabstop_range| {
9630 let mut delta = 0_isize;
9631 insertion_ranges.iter().map(move |insertion_range| {
9632 let insertion_start = insertion_range.start as isize + delta;
9633 delta +=
9634 snippet.text.len() as isize - insertion_range.len() as isize;
9635
9636 let start = ((insertion_start + tabstop_range.start) as usize)
9637 .min(snapshot.len());
9638 let end = ((insertion_start + tabstop_range.end) as usize)
9639 .min(snapshot.len());
9640 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9641 })
9642 })
9643 .collect::<Vec<_>>();
9644 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9645
9646 Tabstop {
9647 is_end_tabstop,
9648 ranges: tabstop_ranges,
9649 choices: tabstop.choices.clone(),
9650 }
9651 })
9652 .collect::<Vec<_>>()
9653 });
9654 if let Some(tabstop) = tabstops.first() {
9655 self.change_selections(Default::default(), window, cx, |s| {
9656 // Reverse order so that the first range is the newest created selection.
9657 // Completions will use it and autoscroll will prioritize it.
9658 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9659 });
9660
9661 if let Some(choices) = &tabstop.choices
9662 && let Some(selection) = tabstop.ranges.first()
9663 {
9664 self.show_snippet_choices(choices, selection.clone(), cx)
9665 }
9666
9667 // If we're already at the last tabstop and it's at the end of the snippet,
9668 // we're done, we don't need to keep the state around.
9669 if !tabstop.is_end_tabstop {
9670 let choices = tabstops
9671 .iter()
9672 .map(|tabstop| tabstop.choices.clone())
9673 .collect();
9674
9675 let ranges = tabstops
9676 .into_iter()
9677 .map(|tabstop| tabstop.ranges)
9678 .collect::<Vec<_>>();
9679
9680 self.snippet_stack.push(SnippetState {
9681 active_index: 0,
9682 ranges,
9683 choices,
9684 });
9685 }
9686
9687 // Check whether the just-entered snippet ends with an auto-closable bracket.
9688 if self.autoclose_regions.is_empty() {
9689 let snapshot = self.buffer.read(cx).snapshot(cx);
9690 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9691 let selection_head = selection.head();
9692 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9693 continue;
9694 };
9695
9696 let mut bracket_pair = None;
9697 let max_lookup_length = scope
9698 .brackets()
9699 .map(|(pair, _)| {
9700 pair.start
9701 .as_str()
9702 .chars()
9703 .count()
9704 .max(pair.end.as_str().chars().count())
9705 })
9706 .max();
9707 if let Some(max_lookup_length) = max_lookup_length {
9708 let next_text = snapshot
9709 .chars_at(selection_head)
9710 .take(max_lookup_length)
9711 .collect::<String>();
9712 let prev_text = snapshot
9713 .reversed_chars_at(selection_head)
9714 .take(max_lookup_length)
9715 .collect::<String>();
9716
9717 for (pair, enabled) in scope.brackets() {
9718 if enabled
9719 && pair.close
9720 && prev_text.starts_with(pair.start.as_str())
9721 && next_text.starts_with(pair.end.as_str())
9722 {
9723 bracket_pair = Some(pair.clone());
9724 break;
9725 }
9726 }
9727 }
9728
9729 if let Some(pair) = bracket_pair {
9730 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9731 let autoclose_enabled =
9732 self.use_autoclose && snapshot_settings.use_autoclose;
9733 if autoclose_enabled {
9734 let start = snapshot.anchor_after(selection_head);
9735 let end = snapshot.anchor_after(selection_head);
9736 self.autoclose_regions.push(AutocloseRegion {
9737 selection_id: selection.id,
9738 range: start..end,
9739 pair,
9740 });
9741 }
9742 }
9743 }
9744 }
9745 }
9746 Ok(())
9747 }
9748
9749 pub fn move_to_next_snippet_tabstop(
9750 &mut self,
9751 window: &mut Window,
9752 cx: &mut Context<Self>,
9753 ) -> bool {
9754 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9755 }
9756
9757 pub fn move_to_prev_snippet_tabstop(
9758 &mut self,
9759 window: &mut Window,
9760 cx: &mut Context<Self>,
9761 ) -> bool {
9762 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9763 }
9764
9765 pub fn move_to_snippet_tabstop(
9766 &mut self,
9767 bias: Bias,
9768 window: &mut Window,
9769 cx: &mut Context<Self>,
9770 ) -> bool {
9771 if let Some(mut snippet) = self.snippet_stack.pop() {
9772 match bias {
9773 Bias::Left => {
9774 if snippet.active_index > 0 {
9775 snippet.active_index -= 1;
9776 } else {
9777 self.snippet_stack.push(snippet);
9778 return false;
9779 }
9780 }
9781 Bias::Right => {
9782 if snippet.active_index + 1 < snippet.ranges.len() {
9783 snippet.active_index += 1;
9784 } else {
9785 self.snippet_stack.push(snippet);
9786 return false;
9787 }
9788 }
9789 }
9790 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9791 self.change_selections(Default::default(), window, cx, |s| {
9792 // Reverse order so that the first range is the newest created selection.
9793 // Completions will use it and autoscroll will prioritize it.
9794 s.select_ranges(current_ranges.iter().rev().cloned())
9795 });
9796
9797 if let Some(choices) = &snippet.choices[snippet.active_index]
9798 && let Some(selection) = current_ranges.first()
9799 {
9800 self.show_snippet_choices(choices, selection.clone(), cx);
9801 }
9802
9803 // If snippet state is not at the last tabstop, push it back on the stack
9804 if snippet.active_index + 1 < snippet.ranges.len() {
9805 self.snippet_stack.push(snippet);
9806 }
9807 return true;
9808 }
9809 }
9810
9811 false
9812 }
9813
9814 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9815 self.transact(window, cx, |this, window, cx| {
9816 this.select_all(&SelectAll, window, cx);
9817 this.insert("", window, cx);
9818 });
9819 }
9820
9821 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9822 if self.read_only(cx) {
9823 return;
9824 }
9825 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9826 self.transact(window, cx, |this, window, cx| {
9827 this.select_autoclose_pair(window, cx);
9828
9829 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9830
9831 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9832 if !this.linked_edit_ranges.is_empty() {
9833 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9834 let snapshot = this.buffer.read(cx).snapshot(cx);
9835
9836 for selection in selections.iter() {
9837 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9838 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9839 if selection_start.buffer_id != selection_end.buffer_id {
9840 continue;
9841 }
9842 if let Some(ranges) =
9843 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9844 {
9845 for (buffer, entries) in ranges {
9846 linked_ranges.entry(buffer).or_default().extend(entries);
9847 }
9848 }
9849 }
9850 }
9851
9852 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9853 for selection in &mut selections {
9854 if selection.is_empty() {
9855 let old_head = selection.head();
9856 let mut new_head =
9857 movement::left(&display_map, old_head.to_display_point(&display_map))
9858 .to_point(&display_map);
9859 if let Some((buffer, line_buffer_range)) = display_map
9860 .buffer_snapshot()
9861 .buffer_line_for_row(MultiBufferRow(old_head.row))
9862 {
9863 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9864 let indent_len = match indent_size.kind {
9865 IndentKind::Space => {
9866 buffer.settings_at(line_buffer_range.start, cx).tab_size
9867 }
9868 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9869 };
9870 if old_head.column <= indent_size.len && old_head.column > 0 {
9871 let indent_len = indent_len.get();
9872 new_head = cmp::min(
9873 new_head,
9874 MultiBufferPoint::new(
9875 old_head.row,
9876 ((old_head.column - 1) / indent_len) * indent_len,
9877 ),
9878 );
9879 }
9880 }
9881
9882 selection.set_head(new_head, SelectionGoal::None);
9883 }
9884 }
9885
9886 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9887 this.insert("", window, cx);
9888 let empty_str: Arc<str> = Arc::from("");
9889 for (buffer, edits) in linked_ranges {
9890 let snapshot = buffer.read(cx).snapshot();
9891 use text::ToPoint as TP;
9892
9893 let edits = edits
9894 .into_iter()
9895 .map(|range| {
9896 let end_point = TP::to_point(&range.end, &snapshot);
9897 let mut start_point = TP::to_point(&range.start, &snapshot);
9898
9899 if end_point == start_point {
9900 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9901 .saturating_sub(1);
9902 start_point =
9903 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9904 };
9905
9906 (start_point..end_point, empty_str.clone())
9907 })
9908 .sorted_by_key(|(range, _)| range.start)
9909 .collect::<Vec<_>>();
9910 buffer.update(cx, |this, cx| {
9911 this.edit(edits, None, cx);
9912 })
9913 }
9914 this.refresh_edit_prediction(true, false, window, cx);
9915 refresh_linked_ranges(this, window, cx);
9916 });
9917 }
9918
9919 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9920 if self.read_only(cx) {
9921 return;
9922 }
9923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9924 self.transact(window, cx, |this, window, cx| {
9925 this.change_selections(Default::default(), window, cx, |s| {
9926 s.move_with(|map, selection| {
9927 if selection.is_empty() {
9928 let cursor = movement::right(map, selection.head());
9929 selection.end = cursor;
9930 selection.reversed = true;
9931 selection.goal = SelectionGoal::None;
9932 }
9933 })
9934 });
9935 this.insert("", window, cx);
9936 this.refresh_edit_prediction(true, false, window, cx);
9937 });
9938 }
9939
9940 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9941 if self.mode.is_single_line() {
9942 cx.propagate();
9943 return;
9944 }
9945
9946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9947 if self.move_to_prev_snippet_tabstop(window, cx) {
9948 return;
9949 }
9950 self.outdent(&Outdent, window, cx);
9951 }
9952
9953 pub fn next_snippet_tabstop(
9954 &mut self,
9955 _: &NextSnippetTabstop,
9956 window: &mut Window,
9957 cx: &mut Context<Self>,
9958 ) {
9959 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
9960 return;
9961 }
9962
9963 if self.move_to_next_snippet_tabstop(window, cx) {
9964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9965 return;
9966 }
9967 }
9968
9969 pub fn previous_snippet_tabstop(
9970 &mut self,
9971 _: &PreviousSnippetTabstop,
9972 window: &mut Window,
9973 cx: &mut Context<Self>,
9974 ) {
9975 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
9976 return;
9977 }
9978
9979 if self.move_to_prev_snippet_tabstop(window, cx) {
9980 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9981 return;
9982 }
9983 }
9984
9985 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9986 if self.mode.is_single_line() {
9987 cx.propagate();
9988 return;
9989 }
9990
9991 if self.move_to_next_snippet_tabstop(window, cx) {
9992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9993 return;
9994 }
9995 if self.read_only(cx) {
9996 return;
9997 }
9998 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9999 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10000 let buffer = self.buffer.read(cx);
10001 let snapshot = buffer.snapshot(cx);
10002 let rows_iter = selections.iter().map(|s| s.head().row);
10003 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10004
10005 let has_some_cursor_in_whitespace = selections
10006 .iter()
10007 .filter(|selection| selection.is_empty())
10008 .any(|selection| {
10009 let cursor = selection.head();
10010 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10011 cursor.column < current_indent.len
10012 });
10013
10014 let mut edits = Vec::new();
10015 let mut prev_edited_row = 0;
10016 let mut row_delta = 0;
10017 for selection in &mut selections {
10018 if selection.start.row != prev_edited_row {
10019 row_delta = 0;
10020 }
10021 prev_edited_row = selection.end.row;
10022
10023 // If the selection is non-empty, then increase the indentation of the selected lines.
10024 if !selection.is_empty() {
10025 row_delta =
10026 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10027 continue;
10028 }
10029
10030 let cursor = selection.head();
10031 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10032 if let Some(suggested_indent) =
10033 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10034 {
10035 // Don't do anything if already at suggested indent
10036 // and there is any other cursor which is not
10037 if has_some_cursor_in_whitespace
10038 && cursor.column == current_indent.len
10039 && current_indent.len == suggested_indent.len
10040 {
10041 continue;
10042 }
10043
10044 // Adjust line and move cursor to suggested indent
10045 // if cursor is not at suggested indent
10046 if cursor.column < suggested_indent.len
10047 && cursor.column <= current_indent.len
10048 && current_indent.len <= suggested_indent.len
10049 {
10050 selection.start = Point::new(cursor.row, suggested_indent.len);
10051 selection.end = selection.start;
10052 if row_delta == 0 {
10053 edits.extend(Buffer::edit_for_indent_size_adjustment(
10054 cursor.row,
10055 current_indent,
10056 suggested_indent,
10057 ));
10058 row_delta = suggested_indent.len - current_indent.len;
10059 }
10060 continue;
10061 }
10062
10063 // If current indent is more than suggested indent
10064 // only move cursor to current indent and skip indent
10065 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10066 selection.start = Point::new(cursor.row, current_indent.len);
10067 selection.end = selection.start;
10068 continue;
10069 }
10070 }
10071
10072 // Otherwise, insert a hard or soft tab.
10073 let settings = buffer.language_settings_at(cursor, cx);
10074 let tab_size = if settings.hard_tabs {
10075 IndentSize::tab()
10076 } else {
10077 let tab_size = settings.tab_size.get();
10078 let indent_remainder = snapshot
10079 .text_for_range(Point::new(cursor.row, 0)..cursor)
10080 .flat_map(str::chars)
10081 .fold(row_delta % tab_size, |counter: u32, c| {
10082 if c == '\t' {
10083 0
10084 } else {
10085 (counter + 1) % tab_size
10086 }
10087 });
10088
10089 let chars_to_next_tab_stop = tab_size - indent_remainder;
10090 IndentSize::spaces(chars_to_next_tab_stop)
10091 };
10092 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10093 selection.end = selection.start;
10094 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10095 row_delta += tab_size.len;
10096 }
10097
10098 self.transact(window, cx, |this, window, cx| {
10099 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10100 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10101 this.refresh_edit_prediction(true, false, window, cx);
10102 });
10103 }
10104
10105 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10106 if self.read_only(cx) {
10107 return;
10108 }
10109 if self.mode.is_single_line() {
10110 cx.propagate();
10111 return;
10112 }
10113
10114 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10115 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10116 let mut prev_edited_row = 0;
10117 let mut row_delta = 0;
10118 let mut edits = Vec::new();
10119 let buffer = self.buffer.read(cx);
10120 let snapshot = buffer.snapshot(cx);
10121 for selection in &mut selections {
10122 if selection.start.row != prev_edited_row {
10123 row_delta = 0;
10124 }
10125 prev_edited_row = selection.end.row;
10126
10127 row_delta =
10128 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10129 }
10130
10131 self.transact(window, cx, |this, window, cx| {
10132 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10133 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10134 });
10135 }
10136
10137 fn indent_selection(
10138 buffer: &MultiBuffer,
10139 snapshot: &MultiBufferSnapshot,
10140 selection: &mut Selection<Point>,
10141 edits: &mut Vec<(Range<Point>, String)>,
10142 delta_for_start_row: u32,
10143 cx: &App,
10144 ) -> u32 {
10145 let settings = buffer.language_settings_at(selection.start, cx);
10146 let tab_size = settings.tab_size.get();
10147 let indent_kind = if settings.hard_tabs {
10148 IndentKind::Tab
10149 } else {
10150 IndentKind::Space
10151 };
10152 let mut start_row = selection.start.row;
10153 let mut end_row = selection.end.row + 1;
10154
10155 // If a selection ends at the beginning of a line, don't indent
10156 // that last line.
10157 if selection.end.column == 0 && selection.end.row > selection.start.row {
10158 end_row -= 1;
10159 }
10160
10161 // Avoid re-indenting a row that has already been indented by a
10162 // previous selection, but still update this selection's column
10163 // to reflect that indentation.
10164 if delta_for_start_row > 0 {
10165 start_row += 1;
10166 selection.start.column += delta_for_start_row;
10167 if selection.end.row == selection.start.row {
10168 selection.end.column += delta_for_start_row;
10169 }
10170 }
10171
10172 let mut delta_for_end_row = 0;
10173 let has_multiple_rows = start_row + 1 != end_row;
10174 for row in start_row..end_row {
10175 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10176 let indent_delta = match (current_indent.kind, indent_kind) {
10177 (IndentKind::Space, IndentKind::Space) => {
10178 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10179 IndentSize::spaces(columns_to_next_tab_stop)
10180 }
10181 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10182 (_, IndentKind::Tab) => IndentSize::tab(),
10183 };
10184
10185 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10186 0
10187 } else {
10188 selection.start.column
10189 };
10190 let row_start = Point::new(row, start);
10191 edits.push((
10192 row_start..row_start,
10193 indent_delta.chars().collect::<String>(),
10194 ));
10195
10196 // Update this selection's endpoints to reflect the indentation.
10197 if row == selection.start.row {
10198 selection.start.column += indent_delta.len;
10199 }
10200 if row == selection.end.row {
10201 selection.end.column += indent_delta.len;
10202 delta_for_end_row = indent_delta.len;
10203 }
10204 }
10205
10206 if selection.start.row == selection.end.row {
10207 delta_for_start_row + delta_for_end_row
10208 } else {
10209 delta_for_end_row
10210 }
10211 }
10212
10213 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10214 if self.read_only(cx) {
10215 return;
10216 }
10217 if self.mode.is_single_line() {
10218 cx.propagate();
10219 return;
10220 }
10221
10222 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10223 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10224 let selections = self.selections.all::<Point>(&display_map);
10225 let mut deletion_ranges = Vec::new();
10226 let mut last_outdent = None;
10227 {
10228 let buffer = self.buffer.read(cx);
10229 let snapshot = buffer.snapshot(cx);
10230 for selection in &selections {
10231 let settings = buffer.language_settings_at(selection.start, cx);
10232 let tab_size = settings.tab_size.get();
10233 let mut rows = selection.spanned_rows(false, &display_map);
10234
10235 // Avoid re-outdenting a row that has already been outdented by a
10236 // previous selection.
10237 if let Some(last_row) = last_outdent
10238 && last_row == rows.start
10239 {
10240 rows.start = rows.start.next_row();
10241 }
10242 let has_multiple_rows = rows.len() > 1;
10243 for row in rows.iter_rows() {
10244 let indent_size = snapshot.indent_size_for_line(row);
10245 if indent_size.len > 0 {
10246 let deletion_len = match indent_size.kind {
10247 IndentKind::Space => {
10248 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10249 if columns_to_prev_tab_stop == 0 {
10250 tab_size
10251 } else {
10252 columns_to_prev_tab_stop
10253 }
10254 }
10255 IndentKind::Tab => 1,
10256 };
10257 let start = if has_multiple_rows
10258 || deletion_len > selection.start.column
10259 || indent_size.len < selection.start.column
10260 {
10261 0
10262 } else {
10263 selection.start.column - deletion_len
10264 };
10265 deletion_ranges.push(
10266 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10267 );
10268 last_outdent = Some(row);
10269 }
10270 }
10271 }
10272 }
10273
10274 self.transact(window, cx, |this, window, cx| {
10275 this.buffer.update(cx, |buffer, cx| {
10276 let empty_str: Arc<str> = Arc::default();
10277 buffer.edit(
10278 deletion_ranges
10279 .into_iter()
10280 .map(|range| (range, empty_str.clone())),
10281 None,
10282 cx,
10283 );
10284 });
10285 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10286 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10287 });
10288 }
10289
10290 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10291 if self.read_only(cx) {
10292 return;
10293 }
10294 if self.mode.is_single_line() {
10295 cx.propagate();
10296 return;
10297 }
10298
10299 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10300 let selections = self
10301 .selections
10302 .all::<usize>(&self.display_snapshot(cx))
10303 .into_iter()
10304 .map(|s| s.range());
10305
10306 self.transact(window, cx, |this, window, cx| {
10307 this.buffer.update(cx, |buffer, cx| {
10308 buffer.autoindent_ranges(selections, cx);
10309 });
10310 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10311 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10312 });
10313 }
10314
10315 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10317 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10318 let selections = self.selections.all::<Point>(&display_map);
10319
10320 let mut new_cursors = Vec::new();
10321 let mut edit_ranges = Vec::new();
10322 let mut selections = selections.iter().peekable();
10323 while let Some(selection) = selections.next() {
10324 let mut rows = selection.spanned_rows(false, &display_map);
10325
10326 // Accumulate contiguous regions of rows that we want to delete.
10327 while let Some(next_selection) = selections.peek() {
10328 let next_rows = next_selection.spanned_rows(false, &display_map);
10329 if next_rows.start <= rows.end {
10330 rows.end = next_rows.end;
10331 selections.next().unwrap();
10332 } else {
10333 break;
10334 }
10335 }
10336
10337 let buffer = display_map.buffer_snapshot();
10338 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10339 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10340 // If there's a line after the range, delete the \n from the end of the row range
10341 (
10342 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10343 rows.end,
10344 )
10345 } else {
10346 // If there isn't a line after the range, delete the \n from the line before the
10347 // start of the row range
10348 edit_start = edit_start.saturating_sub(1);
10349 (buffer.len(), rows.start.previous_row())
10350 };
10351
10352 let text_layout_details = self.text_layout_details(window);
10353 let x = display_map.x_for_display_point(
10354 selection.head().to_display_point(&display_map),
10355 &text_layout_details,
10356 );
10357 let row = Point::new(target_row.0, 0)
10358 .to_display_point(&display_map)
10359 .row();
10360 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10361
10362 new_cursors.push((
10363 selection.id,
10364 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10365 SelectionGoal::None,
10366 ));
10367 edit_ranges.push(edit_start..edit_end);
10368 }
10369
10370 self.transact(window, cx, |this, window, cx| {
10371 let buffer = this.buffer.update(cx, |buffer, cx| {
10372 let empty_str: Arc<str> = Arc::default();
10373 buffer.edit(
10374 edit_ranges
10375 .into_iter()
10376 .map(|range| (range, empty_str.clone())),
10377 None,
10378 cx,
10379 );
10380 buffer.snapshot(cx)
10381 });
10382 let new_selections = new_cursors
10383 .into_iter()
10384 .map(|(id, cursor, goal)| {
10385 let cursor = cursor.to_point(&buffer);
10386 Selection {
10387 id,
10388 start: cursor,
10389 end: cursor,
10390 reversed: false,
10391 goal,
10392 }
10393 })
10394 .collect();
10395
10396 this.change_selections(Default::default(), window, cx, |s| {
10397 s.select(new_selections);
10398 });
10399 });
10400 }
10401
10402 pub fn join_lines_impl(
10403 &mut self,
10404 insert_whitespace: bool,
10405 window: &mut Window,
10406 cx: &mut Context<Self>,
10407 ) {
10408 if self.read_only(cx) {
10409 return;
10410 }
10411 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10412 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10413 let start = MultiBufferRow(selection.start.row);
10414 // Treat single line selections as if they include the next line. Otherwise this action
10415 // would do nothing for single line selections individual cursors.
10416 let end = if selection.start.row == selection.end.row {
10417 MultiBufferRow(selection.start.row + 1)
10418 } else {
10419 MultiBufferRow(selection.end.row)
10420 };
10421
10422 if let Some(last_row_range) = row_ranges.last_mut()
10423 && start <= last_row_range.end
10424 {
10425 last_row_range.end = end;
10426 continue;
10427 }
10428 row_ranges.push(start..end);
10429 }
10430
10431 let snapshot = self.buffer.read(cx).snapshot(cx);
10432 let mut cursor_positions = Vec::new();
10433 for row_range in &row_ranges {
10434 let anchor = snapshot.anchor_before(Point::new(
10435 row_range.end.previous_row().0,
10436 snapshot.line_len(row_range.end.previous_row()),
10437 ));
10438 cursor_positions.push(anchor..anchor);
10439 }
10440
10441 self.transact(window, cx, |this, window, cx| {
10442 for row_range in row_ranges.into_iter().rev() {
10443 for row in row_range.iter_rows().rev() {
10444 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10445 let next_line_row = row.next_row();
10446 let indent = snapshot.indent_size_for_line(next_line_row);
10447 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10448
10449 let replace =
10450 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10451 " "
10452 } else {
10453 ""
10454 };
10455
10456 this.buffer.update(cx, |buffer, cx| {
10457 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10458 });
10459 }
10460 }
10461
10462 this.change_selections(Default::default(), window, cx, |s| {
10463 s.select_anchor_ranges(cursor_positions)
10464 });
10465 });
10466 }
10467
10468 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10470 self.join_lines_impl(true, window, cx);
10471 }
10472
10473 pub fn sort_lines_case_sensitive(
10474 &mut self,
10475 _: &SortLinesCaseSensitive,
10476 window: &mut Window,
10477 cx: &mut Context<Self>,
10478 ) {
10479 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10480 }
10481
10482 pub fn sort_lines_by_length(
10483 &mut self,
10484 _: &SortLinesByLength,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 self.manipulate_immutable_lines(window, cx, |lines| {
10489 lines.sort_by_key(|&line| line.chars().count())
10490 })
10491 }
10492
10493 pub fn sort_lines_case_insensitive(
10494 &mut self,
10495 _: &SortLinesCaseInsensitive,
10496 window: &mut Window,
10497 cx: &mut Context<Self>,
10498 ) {
10499 self.manipulate_immutable_lines(window, cx, |lines| {
10500 lines.sort_by_key(|line| line.to_lowercase())
10501 })
10502 }
10503
10504 pub fn unique_lines_case_insensitive(
10505 &mut self,
10506 _: &UniqueLinesCaseInsensitive,
10507 window: &mut Window,
10508 cx: &mut Context<Self>,
10509 ) {
10510 self.manipulate_immutable_lines(window, cx, |lines| {
10511 let mut seen = HashSet::default();
10512 lines.retain(|line| seen.insert(line.to_lowercase()));
10513 })
10514 }
10515
10516 pub fn unique_lines_case_sensitive(
10517 &mut self,
10518 _: &UniqueLinesCaseSensitive,
10519 window: &mut Window,
10520 cx: &mut Context<Self>,
10521 ) {
10522 self.manipulate_immutable_lines(window, cx, |lines| {
10523 let mut seen = HashSet::default();
10524 lines.retain(|line| seen.insert(*line));
10525 })
10526 }
10527
10528 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10529 let snapshot = self.buffer.read(cx).snapshot(cx);
10530 for selection in self.selections.disjoint_anchors_arc().iter() {
10531 if snapshot
10532 .language_at(selection.start)
10533 .and_then(|lang| lang.config().wrap_characters.as_ref())
10534 .is_some()
10535 {
10536 return true;
10537 }
10538 }
10539 false
10540 }
10541
10542 fn wrap_selections_in_tag(
10543 &mut self,
10544 _: &WrapSelectionsInTag,
10545 window: &mut Window,
10546 cx: &mut Context<Self>,
10547 ) {
10548 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10549
10550 let snapshot = self.buffer.read(cx).snapshot(cx);
10551
10552 let mut edits = Vec::new();
10553 let mut boundaries = Vec::new();
10554
10555 for selection in self
10556 .selections
10557 .all_adjusted(&self.display_snapshot(cx))
10558 .iter()
10559 {
10560 let Some(wrap_config) = snapshot
10561 .language_at(selection.start)
10562 .and_then(|lang| lang.config().wrap_characters.clone())
10563 else {
10564 continue;
10565 };
10566
10567 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10568 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10569
10570 let start_before = snapshot.anchor_before(selection.start);
10571 let end_after = snapshot.anchor_after(selection.end);
10572
10573 edits.push((start_before..start_before, open_tag));
10574 edits.push((end_after..end_after, close_tag));
10575
10576 boundaries.push((
10577 start_before,
10578 end_after,
10579 wrap_config.start_prefix.len(),
10580 wrap_config.end_suffix.len(),
10581 ));
10582 }
10583
10584 if edits.is_empty() {
10585 return;
10586 }
10587
10588 self.transact(window, cx, |this, window, cx| {
10589 let buffer = this.buffer.update(cx, |buffer, cx| {
10590 buffer.edit(edits, None, cx);
10591 buffer.snapshot(cx)
10592 });
10593
10594 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10595 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10596 boundaries.into_iter()
10597 {
10598 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10599 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10600 new_selections.push(open_offset..open_offset);
10601 new_selections.push(close_offset..close_offset);
10602 }
10603
10604 this.change_selections(Default::default(), window, cx, |s| {
10605 s.select_ranges(new_selections);
10606 });
10607
10608 this.request_autoscroll(Autoscroll::fit(), cx);
10609 });
10610 }
10611
10612 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10613 let Some(project) = self.project.clone() else {
10614 return;
10615 };
10616 self.reload(project, window, cx)
10617 .detach_and_notify_err(window, cx);
10618 }
10619
10620 pub fn restore_file(
10621 &mut self,
10622 _: &::git::RestoreFile,
10623 window: &mut Window,
10624 cx: &mut Context<Self>,
10625 ) {
10626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10627 let mut buffer_ids = HashSet::default();
10628 let snapshot = self.buffer().read(cx).snapshot(cx);
10629 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10630 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10631 }
10632
10633 let buffer = self.buffer().read(cx);
10634 let ranges = buffer_ids
10635 .into_iter()
10636 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10637 .collect::<Vec<_>>();
10638
10639 self.restore_hunks_in_ranges(ranges, window, cx);
10640 }
10641
10642 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10643 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10644 let selections = self
10645 .selections
10646 .all(&self.display_snapshot(cx))
10647 .into_iter()
10648 .map(|s| s.range())
10649 .collect();
10650 self.restore_hunks_in_ranges(selections, window, cx);
10651 }
10652
10653 pub fn restore_hunks_in_ranges(
10654 &mut self,
10655 ranges: Vec<Range<Point>>,
10656 window: &mut Window,
10657 cx: &mut Context<Editor>,
10658 ) {
10659 let mut revert_changes = HashMap::default();
10660 let chunk_by = self
10661 .snapshot(window, cx)
10662 .hunks_for_ranges(ranges)
10663 .into_iter()
10664 .chunk_by(|hunk| hunk.buffer_id);
10665 for (buffer_id, hunks) in &chunk_by {
10666 let hunks = hunks.collect::<Vec<_>>();
10667 for hunk in &hunks {
10668 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10669 }
10670 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10671 }
10672 drop(chunk_by);
10673 if !revert_changes.is_empty() {
10674 self.transact(window, cx, |editor, window, cx| {
10675 editor.restore(revert_changes, window, cx);
10676 });
10677 }
10678 }
10679
10680 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10681 if let Some(status) = self
10682 .addons
10683 .iter()
10684 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10685 {
10686 return Some(status);
10687 }
10688 self.project
10689 .as_ref()?
10690 .read(cx)
10691 .status_for_buffer_id(buffer_id, cx)
10692 }
10693
10694 pub fn open_active_item_in_terminal(
10695 &mut self,
10696 _: &OpenInTerminal,
10697 window: &mut Window,
10698 cx: &mut Context<Self>,
10699 ) {
10700 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10701 let project_path = buffer.read(cx).project_path(cx)?;
10702 let project = self.project()?.read(cx);
10703 let entry = project.entry_for_path(&project_path, cx)?;
10704 let parent = match &entry.canonical_path {
10705 Some(canonical_path) => canonical_path.to_path_buf(),
10706 None => project.absolute_path(&project_path, cx)?,
10707 }
10708 .parent()?
10709 .to_path_buf();
10710 Some(parent)
10711 }) {
10712 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10713 }
10714 }
10715
10716 fn set_breakpoint_context_menu(
10717 &mut self,
10718 display_row: DisplayRow,
10719 position: Option<Anchor>,
10720 clicked_point: gpui::Point<Pixels>,
10721 window: &mut Window,
10722 cx: &mut Context<Self>,
10723 ) {
10724 let source = self
10725 .buffer
10726 .read(cx)
10727 .snapshot(cx)
10728 .anchor_before(Point::new(display_row.0, 0u32));
10729
10730 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10731
10732 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10733 self,
10734 source,
10735 clicked_point,
10736 context_menu,
10737 window,
10738 cx,
10739 );
10740 }
10741
10742 fn add_edit_breakpoint_block(
10743 &mut self,
10744 anchor: Anchor,
10745 breakpoint: &Breakpoint,
10746 edit_action: BreakpointPromptEditAction,
10747 window: &mut Window,
10748 cx: &mut Context<Self>,
10749 ) {
10750 let weak_editor = cx.weak_entity();
10751 let bp_prompt = cx.new(|cx| {
10752 BreakpointPromptEditor::new(
10753 weak_editor,
10754 anchor,
10755 breakpoint.clone(),
10756 edit_action,
10757 window,
10758 cx,
10759 )
10760 });
10761
10762 let height = bp_prompt.update(cx, |this, cx| {
10763 this.prompt
10764 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10765 });
10766 let cloned_prompt = bp_prompt.clone();
10767 let blocks = vec![BlockProperties {
10768 style: BlockStyle::Sticky,
10769 placement: BlockPlacement::Above(anchor),
10770 height: Some(height),
10771 render: Arc::new(move |cx| {
10772 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10773 cloned_prompt.clone().into_any_element()
10774 }),
10775 priority: 0,
10776 }];
10777
10778 let focus_handle = bp_prompt.focus_handle(cx);
10779 window.focus(&focus_handle);
10780
10781 let block_ids = self.insert_blocks(blocks, None, cx);
10782 bp_prompt.update(cx, |prompt, _| {
10783 prompt.add_block_ids(block_ids);
10784 });
10785 }
10786
10787 pub(crate) fn breakpoint_at_row(
10788 &self,
10789 row: u32,
10790 window: &mut Window,
10791 cx: &mut Context<Self>,
10792 ) -> Option<(Anchor, Breakpoint)> {
10793 let snapshot = self.snapshot(window, cx);
10794 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10795
10796 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10797 }
10798
10799 pub(crate) fn breakpoint_at_anchor(
10800 &self,
10801 breakpoint_position: Anchor,
10802 snapshot: &EditorSnapshot,
10803 cx: &mut Context<Self>,
10804 ) -> Option<(Anchor, Breakpoint)> {
10805 let buffer = self
10806 .buffer
10807 .read(cx)
10808 .buffer_for_anchor(breakpoint_position, cx)?;
10809
10810 let enclosing_excerpt = breakpoint_position.excerpt_id;
10811 let buffer_snapshot = buffer.read(cx).snapshot();
10812
10813 let row = buffer_snapshot
10814 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10815 .row;
10816
10817 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10818 let anchor_end = snapshot
10819 .buffer_snapshot()
10820 .anchor_after(Point::new(row, line_len));
10821
10822 self.breakpoint_store
10823 .as_ref()?
10824 .read_with(cx, |breakpoint_store, cx| {
10825 breakpoint_store
10826 .breakpoints(
10827 &buffer,
10828 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10829 &buffer_snapshot,
10830 cx,
10831 )
10832 .next()
10833 .and_then(|(bp, _)| {
10834 let breakpoint_row = buffer_snapshot
10835 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10836 .row;
10837
10838 if breakpoint_row == row {
10839 snapshot
10840 .buffer_snapshot()
10841 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10842 .map(|position| (position, bp.bp.clone()))
10843 } else {
10844 None
10845 }
10846 })
10847 })
10848 }
10849
10850 pub fn edit_log_breakpoint(
10851 &mut self,
10852 _: &EditLogBreakpoint,
10853 window: &mut Window,
10854 cx: &mut Context<Self>,
10855 ) {
10856 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10857 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10858 message: None,
10859 state: BreakpointState::Enabled,
10860 condition: None,
10861 hit_condition: None,
10862 });
10863
10864 self.add_edit_breakpoint_block(
10865 anchor,
10866 &breakpoint,
10867 BreakpointPromptEditAction::Log,
10868 window,
10869 cx,
10870 );
10871 }
10872 }
10873
10874 fn breakpoints_at_cursors(
10875 &self,
10876 window: &mut Window,
10877 cx: &mut Context<Self>,
10878 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10879 let snapshot = self.snapshot(window, cx);
10880 let cursors = self
10881 .selections
10882 .disjoint_anchors_arc()
10883 .iter()
10884 .map(|selection| {
10885 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10886
10887 let breakpoint_position = self
10888 .breakpoint_at_row(cursor_position.row, window, cx)
10889 .map(|bp| bp.0)
10890 .unwrap_or_else(|| {
10891 snapshot
10892 .display_snapshot
10893 .buffer_snapshot()
10894 .anchor_after(Point::new(cursor_position.row, 0))
10895 });
10896
10897 let breakpoint = self
10898 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10899 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10900
10901 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10902 })
10903 // 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.
10904 .collect::<HashMap<Anchor, _>>();
10905
10906 cursors.into_iter().collect()
10907 }
10908
10909 pub fn enable_breakpoint(
10910 &mut self,
10911 _: &crate::actions::EnableBreakpoint,
10912 window: &mut Window,
10913 cx: &mut Context<Self>,
10914 ) {
10915 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10916 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10917 continue;
10918 };
10919 self.edit_breakpoint_at_anchor(
10920 anchor,
10921 breakpoint,
10922 BreakpointEditAction::InvertState,
10923 cx,
10924 );
10925 }
10926 }
10927
10928 pub fn disable_breakpoint(
10929 &mut self,
10930 _: &crate::actions::DisableBreakpoint,
10931 window: &mut Window,
10932 cx: &mut Context<Self>,
10933 ) {
10934 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10935 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10936 continue;
10937 };
10938 self.edit_breakpoint_at_anchor(
10939 anchor,
10940 breakpoint,
10941 BreakpointEditAction::InvertState,
10942 cx,
10943 );
10944 }
10945 }
10946
10947 pub fn toggle_breakpoint(
10948 &mut self,
10949 _: &crate::actions::ToggleBreakpoint,
10950 window: &mut Window,
10951 cx: &mut Context<Self>,
10952 ) {
10953 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10954 if let Some(breakpoint) = breakpoint {
10955 self.edit_breakpoint_at_anchor(
10956 anchor,
10957 breakpoint,
10958 BreakpointEditAction::Toggle,
10959 cx,
10960 );
10961 } else {
10962 self.edit_breakpoint_at_anchor(
10963 anchor,
10964 Breakpoint::new_standard(),
10965 BreakpointEditAction::Toggle,
10966 cx,
10967 );
10968 }
10969 }
10970 }
10971
10972 pub fn edit_breakpoint_at_anchor(
10973 &mut self,
10974 breakpoint_position: Anchor,
10975 breakpoint: Breakpoint,
10976 edit_action: BreakpointEditAction,
10977 cx: &mut Context<Self>,
10978 ) {
10979 let Some(breakpoint_store) = &self.breakpoint_store else {
10980 return;
10981 };
10982
10983 let Some(buffer) = self
10984 .buffer
10985 .read(cx)
10986 .buffer_for_anchor(breakpoint_position, cx)
10987 else {
10988 return;
10989 };
10990
10991 breakpoint_store.update(cx, |breakpoint_store, cx| {
10992 breakpoint_store.toggle_breakpoint(
10993 buffer,
10994 BreakpointWithPosition {
10995 position: breakpoint_position.text_anchor,
10996 bp: breakpoint,
10997 },
10998 edit_action,
10999 cx,
11000 );
11001 });
11002
11003 cx.notify();
11004 }
11005
11006 #[cfg(any(test, feature = "test-support"))]
11007 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11008 self.breakpoint_store.clone()
11009 }
11010
11011 pub fn prepare_restore_change(
11012 &self,
11013 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11014 hunk: &MultiBufferDiffHunk,
11015 cx: &mut App,
11016 ) -> Option<()> {
11017 if hunk.is_created_file() {
11018 return None;
11019 }
11020 let buffer = self.buffer.read(cx);
11021 let diff = buffer.diff_for(hunk.buffer_id)?;
11022 let buffer = buffer.buffer(hunk.buffer_id)?;
11023 let buffer = buffer.read(cx);
11024 let original_text = diff
11025 .read(cx)
11026 .base_text()
11027 .as_rope()
11028 .slice(hunk.diff_base_byte_range.clone());
11029 let buffer_snapshot = buffer.snapshot();
11030 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11031 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11032 probe
11033 .0
11034 .start
11035 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11036 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11037 }) {
11038 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11039 Some(())
11040 } else {
11041 None
11042 }
11043 }
11044
11045 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11046 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11047 }
11048
11049 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11050 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11051 }
11052
11053 fn manipulate_lines<M>(
11054 &mut self,
11055 window: &mut Window,
11056 cx: &mut Context<Self>,
11057 mut manipulate: M,
11058 ) where
11059 M: FnMut(&str) -> LineManipulationResult,
11060 {
11061 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11062
11063 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11064 let buffer = self.buffer.read(cx).snapshot(cx);
11065
11066 let mut edits = Vec::new();
11067
11068 let selections = self.selections.all::<Point>(&display_map);
11069 let mut selections = selections.iter().peekable();
11070 let mut contiguous_row_selections = Vec::new();
11071 let mut new_selections = Vec::new();
11072 let mut added_lines = 0;
11073 let mut removed_lines = 0;
11074
11075 while let Some(selection) = selections.next() {
11076 let (start_row, end_row) = consume_contiguous_rows(
11077 &mut contiguous_row_selections,
11078 selection,
11079 &display_map,
11080 &mut selections,
11081 );
11082
11083 let start_point = Point::new(start_row.0, 0);
11084 let end_point = Point::new(
11085 end_row.previous_row().0,
11086 buffer.line_len(end_row.previous_row()),
11087 );
11088 let text = buffer
11089 .text_for_range(start_point..end_point)
11090 .collect::<String>();
11091
11092 let LineManipulationResult {
11093 new_text,
11094 line_count_before,
11095 line_count_after,
11096 } = manipulate(&text);
11097
11098 edits.push((start_point..end_point, new_text));
11099
11100 // Selections must change based on added and removed line count
11101 let start_row =
11102 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11103 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11104 new_selections.push(Selection {
11105 id: selection.id,
11106 start: start_row,
11107 end: end_row,
11108 goal: SelectionGoal::None,
11109 reversed: selection.reversed,
11110 });
11111
11112 if line_count_after > line_count_before {
11113 added_lines += line_count_after - line_count_before;
11114 } else if line_count_before > line_count_after {
11115 removed_lines += line_count_before - line_count_after;
11116 }
11117 }
11118
11119 self.transact(window, cx, |this, window, cx| {
11120 let buffer = this.buffer.update(cx, |buffer, cx| {
11121 buffer.edit(edits, None, cx);
11122 buffer.snapshot(cx)
11123 });
11124
11125 // Recalculate offsets on newly edited buffer
11126 let new_selections = new_selections
11127 .iter()
11128 .map(|s| {
11129 let start_point = Point::new(s.start.0, 0);
11130 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11131 Selection {
11132 id: s.id,
11133 start: buffer.point_to_offset(start_point),
11134 end: buffer.point_to_offset(end_point),
11135 goal: s.goal,
11136 reversed: s.reversed,
11137 }
11138 })
11139 .collect();
11140
11141 this.change_selections(Default::default(), window, cx, |s| {
11142 s.select(new_selections);
11143 });
11144
11145 this.request_autoscroll(Autoscroll::fit(), cx);
11146 });
11147 }
11148
11149 fn manipulate_immutable_lines<Fn>(
11150 &mut self,
11151 window: &mut Window,
11152 cx: &mut Context<Self>,
11153 mut callback: Fn,
11154 ) where
11155 Fn: FnMut(&mut Vec<&str>),
11156 {
11157 self.manipulate_lines(window, cx, |text| {
11158 let mut lines: Vec<&str> = text.split('\n').collect();
11159 let line_count_before = lines.len();
11160
11161 callback(&mut lines);
11162
11163 LineManipulationResult {
11164 new_text: lines.join("\n"),
11165 line_count_before,
11166 line_count_after: lines.len(),
11167 }
11168 });
11169 }
11170
11171 fn manipulate_mutable_lines<Fn>(
11172 &mut self,
11173 window: &mut Window,
11174 cx: &mut Context<Self>,
11175 mut callback: Fn,
11176 ) where
11177 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11178 {
11179 self.manipulate_lines(window, cx, |text| {
11180 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11181 let line_count_before = lines.len();
11182
11183 callback(&mut lines);
11184
11185 LineManipulationResult {
11186 new_text: lines.join("\n"),
11187 line_count_before,
11188 line_count_after: lines.len(),
11189 }
11190 });
11191 }
11192
11193 pub fn convert_indentation_to_spaces(
11194 &mut self,
11195 _: &ConvertIndentationToSpaces,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) {
11199 let settings = self.buffer.read(cx).language_settings(cx);
11200 let tab_size = settings.tab_size.get() as usize;
11201
11202 self.manipulate_mutable_lines(window, cx, |lines| {
11203 // Allocates a reasonably sized scratch buffer once for the whole loop
11204 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11205 // Avoids recomputing spaces that could be inserted many times
11206 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11207 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11208 .collect();
11209
11210 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11211 let mut chars = line.as_ref().chars();
11212 let mut col = 0;
11213 let mut changed = false;
11214
11215 for ch in chars.by_ref() {
11216 match ch {
11217 ' ' => {
11218 reindented_line.push(' ');
11219 col += 1;
11220 }
11221 '\t' => {
11222 // \t are converted to spaces depending on the current column
11223 let spaces_len = tab_size - (col % tab_size);
11224 reindented_line.extend(&space_cache[spaces_len - 1]);
11225 col += spaces_len;
11226 changed = true;
11227 }
11228 _ => {
11229 // If we dont append before break, the character is consumed
11230 reindented_line.push(ch);
11231 break;
11232 }
11233 }
11234 }
11235
11236 if !changed {
11237 reindented_line.clear();
11238 continue;
11239 }
11240 // Append the rest of the line and replace old reference with new one
11241 reindented_line.extend(chars);
11242 *line = Cow::Owned(reindented_line.clone());
11243 reindented_line.clear();
11244 }
11245 });
11246 }
11247
11248 pub fn convert_indentation_to_tabs(
11249 &mut self,
11250 _: &ConvertIndentationToTabs,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 let settings = self.buffer.read(cx).language_settings(cx);
11255 let tab_size = settings.tab_size.get() as usize;
11256
11257 self.manipulate_mutable_lines(window, cx, |lines| {
11258 // Allocates a reasonably sized buffer once for the whole loop
11259 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11260 // Avoids recomputing spaces that could be inserted many times
11261 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11262 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11263 .collect();
11264
11265 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11266 let mut chars = line.chars();
11267 let mut spaces_count = 0;
11268 let mut first_non_indent_char = None;
11269 let mut changed = false;
11270
11271 for ch in chars.by_ref() {
11272 match ch {
11273 ' ' => {
11274 // Keep track of spaces. Append \t when we reach tab_size
11275 spaces_count += 1;
11276 changed = true;
11277 if spaces_count == tab_size {
11278 reindented_line.push('\t');
11279 spaces_count = 0;
11280 }
11281 }
11282 '\t' => {
11283 reindented_line.push('\t');
11284 spaces_count = 0;
11285 }
11286 _ => {
11287 // Dont append it yet, we might have remaining spaces
11288 first_non_indent_char = Some(ch);
11289 break;
11290 }
11291 }
11292 }
11293
11294 if !changed {
11295 reindented_line.clear();
11296 continue;
11297 }
11298 // Remaining spaces that didn't make a full tab stop
11299 if spaces_count > 0 {
11300 reindented_line.extend(&space_cache[spaces_count - 1]);
11301 }
11302 // If we consume an extra character that was not indentation, add it back
11303 if let Some(extra_char) = first_non_indent_char {
11304 reindented_line.push(extra_char);
11305 }
11306 // Append the rest of the line and replace old reference with new one
11307 reindented_line.extend(chars);
11308 *line = Cow::Owned(reindented_line.clone());
11309 reindented_line.clear();
11310 }
11311 });
11312 }
11313
11314 pub fn convert_to_upper_case(
11315 &mut self,
11316 _: &ConvertToUpperCase,
11317 window: &mut Window,
11318 cx: &mut Context<Self>,
11319 ) {
11320 self.manipulate_text(window, cx, |text| text.to_uppercase())
11321 }
11322
11323 pub fn convert_to_lower_case(
11324 &mut self,
11325 _: &ConvertToLowerCase,
11326 window: &mut Window,
11327 cx: &mut Context<Self>,
11328 ) {
11329 self.manipulate_text(window, cx, |text| text.to_lowercase())
11330 }
11331
11332 pub fn convert_to_title_case(
11333 &mut self,
11334 _: &ConvertToTitleCase,
11335 window: &mut Window,
11336 cx: &mut Context<Self>,
11337 ) {
11338 self.manipulate_text(window, cx, |text| {
11339 text.split('\n')
11340 .map(|line| line.to_case(Case::Title))
11341 .join("\n")
11342 })
11343 }
11344
11345 pub fn convert_to_snake_case(
11346 &mut self,
11347 _: &ConvertToSnakeCase,
11348 window: &mut Window,
11349 cx: &mut Context<Self>,
11350 ) {
11351 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11352 }
11353
11354 pub fn convert_to_kebab_case(
11355 &mut self,
11356 _: &ConvertToKebabCase,
11357 window: &mut Window,
11358 cx: &mut Context<Self>,
11359 ) {
11360 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11361 }
11362
11363 pub fn convert_to_upper_camel_case(
11364 &mut self,
11365 _: &ConvertToUpperCamelCase,
11366 window: &mut Window,
11367 cx: &mut Context<Self>,
11368 ) {
11369 self.manipulate_text(window, cx, |text| {
11370 text.split('\n')
11371 .map(|line| line.to_case(Case::UpperCamel))
11372 .join("\n")
11373 })
11374 }
11375
11376 pub fn convert_to_lower_camel_case(
11377 &mut self,
11378 _: &ConvertToLowerCamelCase,
11379 window: &mut Window,
11380 cx: &mut Context<Self>,
11381 ) {
11382 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11383 }
11384
11385 pub fn convert_to_opposite_case(
11386 &mut self,
11387 _: &ConvertToOppositeCase,
11388 window: &mut Window,
11389 cx: &mut Context<Self>,
11390 ) {
11391 self.manipulate_text(window, cx, |text| {
11392 text.chars()
11393 .fold(String::with_capacity(text.len()), |mut t, c| {
11394 if c.is_uppercase() {
11395 t.extend(c.to_lowercase());
11396 } else {
11397 t.extend(c.to_uppercase());
11398 }
11399 t
11400 })
11401 })
11402 }
11403
11404 pub fn convert_to_sentence_case(
11405 &mut self,
11406 _: &ConvertToSentenceCase,
11407 window: &mut Window,
11408 cx: &mut Context<Self>,
11409 ) {
11410 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11411 }
11412
11413 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11414 self.manipulate_text(window, cx, |text| {
11415 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11416 if has_upper_case_characters {
11417 text.to_lowercase()
11418 } else {
11419 text.to_uppercase()
11420 }
11421 })
11422 }
11423
11424 pub fn convert_to_rot13(
11425 &mut self,
11426 _: &ConvertToRot13,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) {
11430 self.manipulate_text(window, cx, |text| {
11431 text.chars()
11432 .map(|c| match c {
11433 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11434 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11435 _ => c,
11436 })
11437 .collect()
11438 })
11439 }
11440
11441 pub fn convert_to_rot47(
11442 &mut self,
11443 _: &ConvertToRot47,
11444 window: &mut Window,
11445 cx: &mut Context<Self>,
11446 ) {
11447 self.manipulate_text(window, cx, |text| {
11448 text.chars()
11449 .map(|c| {
11450 let code_point = c as u32;
11451 if code_point >= 33 && code_point <= 126 {
11452 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11453 }
11454 c
11455 })
11456 .collect()
11457 })
11458 }
11459
11460 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11461 where
11462 Fn: FnMut(&str) -> String,
11463 {
11464 let buffer = self.buffer.read(cx).snapshot(cx);
11465
11466 let mut new_selections = Vec::new();
11467 let mut edits = Vec::new();
11468 let mut selection_adjustment = 0i32;
11469
11470 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11471 let selection_is_empty = selection.is_empty();
11472
11473 let (start, end) = if selection_is_empty {
11474 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11475 (word_range.start, word_range.end)
11476 } else {
11477 (
11478 buffer.point_to_offset(selection.start),
11479 buffer.point_to_offset(selection.end),
11480 )
11481 };
11482
11483 let text = buffer.text_for_range(start..end).collect::<String>();
11484 let old_length = text.len() as i32;
11485 let text = callback(&text);
11486
11487 new_selections.push(Selection {
11488 start: (start as i32 - selection_adjustment) as usize,
11489 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11490 goal: SelectionGoal::None,
11491 id: selection.id,
11492 reversed: selection.reversed,
11493 });
11494
11495 selection_adjustment += old_length - text.len() as i32;
11496
11497 edits.push((start..end, text));
11498 }
11499
11500 self.transact(window, cx, |this, window, cx| {
11501 this.buffer.update(cx, |buffer, cx| {
11502 buffer.edit(edits, None, cx);
11503 });
11504
11505 this.change_selections(Default::default(), window, cx, |s| {
11506 s.select(new_selections);
11507 });
11508
11509 this.request_autoscroll(Autoscroll::fit(), cx);
11510 });
11511 }
11512
11513 pub fn move_selection_on_drop(
11514 &mut self,
11515 selection: &Selection<Anchor>,
11516 target: DisplayPoint,
11517 is_cut: bool,
11518 window: &mut Window,
11519 cx: &mut Context<Self>,
11520 ) {
11521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11522 let buffer = display_map.buffer_snapshot();
11523 let mut edits = Vec::new();
11524 let insert_point = display_map
11525 .clip_point(target, Bias::Left)
11526 .to_point(&display_map);
11527 let text = buffer
11528 .text_for_range(selection.start..selection.end)
11529 .collect::<String>();
11530 if is_cut {
11531 edits.push(((selection.start..selection.end), String::new()));
11532 }
11533 let insert_anchor = buffer.anchor_before(insert_point);
11534 edits.push(((insert_anchor..insert_anchor), text));
11535 let last_edit_start = insert_anchor.bias_left(buffer);
11536 let last_edit_end = insert_anchor.bias_right(buffer);
11537 self.transact(window, cx, |this, window, cx| {
11538 this.buffer.update(cx, |buffer, cx| {
11539 buffer.edit(edits, None, cx);
11540 });
11541 this.change_selections(Default::default(), window, cx, |s| {
11542 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11543 });
11544 });
11545 }
11546
11547 pub fn clear_selection_drag_state(&mut self) {
11548 self.selection_drag_state = SelectionDragState::None;
11549 }
11550
11551 pub fn duplicate(
11552 &mut self,
11553 upwards: bool,
11554 whole_lines: bool,
11555 window: &mut Window,
11556 cx: &mut Context<Self>,
11557 ) {
11558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11559
11560 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11561 let buffer = display_map.buffer_snapshot();
11562 let selections = self.selections.all::<Point>(&display_map);
11563
11564 let mut edits = Vec::new();
11565 let mut selections_iter = selections.iter().peekable();
11566 while let Some(selection) = selections_iter.next() {
11567 let mut rows = selection.spanned_rows(false, &display_map);
11568 // duplicate line-wise
11569 if whole_lines || selection.start == selection.end {
11570 // Avoid duplicating the same lines twice.
11571 while let Some(next_selection) = selections_iter.peek() {
11572 let next_rows = next_selection.spanned_rows(false, &display_map);
11573 if next_rows.start < rows.end {
11574 rows.end = next_rows.end;
11575 selections_iter.next().unwrap();
11576 } else {
11577 break;
11578 }
11579 }
11580
11581 // Copy the text from the selected row region and splice it either at the start
11582 // or end of the region.
11583 let start = Point::new(rows.start.0, 0);
11584 let end = Point::new(
11585 rows.end.previous_row().0,
11586 buffer.line_len(rows.end.previous_row()),
11587 );
11588
11589 let mut text = buffer.text_for_range(start..end).collect::<String>();
11590
11591 let insert_location = if upwards {
11592 // When duplicating upward, we need to insert before the current line.
11593 // If we're on the last line and it doesn't end with a newline,
11594 // we need to add a newline before the duplicated content.
11595 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11596 && buffer.max_point().column > 0
11597 && !text.ends_with('\n');
11598
11599 if needs_leading_newline {
11600 text.insert(0, '\n');
11601 end
11602 } else {
11603 text.push('\n');
11604 Point::new(rows.start.0, 0)
11605 }
11606 } else {
11607 text.push('\n');
11608 start
11609 };
11610 edits.push((insert_location..insert_location, text));
11611 } else {
11612 // duplicate character-wise
11613 let start = selection.start;
11614 let end = selection.end;
11615 let text = buffer.text_for_range(start..end).collect::<String>();
11616 edits.push((selection.end..selection.end, text));
11617 }
11618 }
11619
11620 self.transact(window, cx, |this, window, cx| {
11621 this.buffer.update(cx, |buffer, cx| {
11622 buffer.edit(edits, None, cx);
11623 });
11624
11625 // When duplicating upward with whole lines, move the cursor to the duplicated line
11626 if upwards && whole_lines {
11627 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11628
11629 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11630 let mut new_ranges = Vec::new();
11631 let selections = s.all::<Point>(&display_map);
11632 let mut selections_iter = selections.iter().peekable();
11633
11634 while let Some(first_selection) = selections_iter.next() {
11635 // Group contiguous selections together to find the total row span
11636 let mut group_selections = vec![first_selection];
11637 let mut rows = first_selection.spanned_rows(false, &display_map);
11638
11639 while let Some(next_selection) = selections_iter.peek() {
11640 let next_rows = next_selection.spanned_rows(false, &display_map);
11641 if next_rows.start < rows.end {
11642 rows.end = next_rows.end;
11643 group_selections.push(selections_iter.next().unwrap());
11644 } else {
11645 break;
11646 }
11647 }
11648
11649 let row_count = rows.end.0 - rows.start.0;
11650
11651 // Move all selections in this group up by the total number of duplicated rows
11652 for selection in group_selections {
11653 let new_start = Point::new(
11654 selection.start.row.saturating_sub(row_count),
11655 selection.start.column,
11656 );
11657
11658 let new_end = Point::new(
11659 selection.end.row.saturating_sub(row_count),
11660 selection.end.column,
11661 );
11662
11663 new_ranges.push(new_start..new_end);
11664 }
11665 }
11666
11667 s.select_ranges(new_ranges);
11668 });
11669 }
11670
11671 this.request_autoscroll(Autoscroll::fit(), cx);
11672 });
11673 }
11674
11675 pub fn duplicate_line_up(
11676 &mut self,
11677 _: &DuplicateLineUp,
11678 window: &mut Window,
11679 cx: &mut Context<Self>,
11680 ) {
11681 self.duplicate(true, true, window, cx);
11682 }
11683
11684 pub fn duplicate_line_down(
11685 &mut self,
11686 _: &DuplicateLineDown,
11687 window: &mut Window,
11688 cx: &mut Context<Self>,
11689 ) {
11690 self.duplicate(false, true, window, cx);
11691 }
11692
11693 pub fn duplicate_selection(
11694 &mut self,
11695 _: &DuplicateSelection,
11696 window: &mut Window,
11697 cx: &mut Context<Self>,
11698 ) {
11699 self.duplicate(false, false, window, cx);
11700 }
11701
11702 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11703 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11704 if self.mode.is_single_line() {
11705 cx.propagate();
11706 return;
11707 }
11708
11709 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11710 let buffer = self.buffer.read(cx).snapshot(cx);
11711
11712 let mut edits = Vec::new();
11713 let mut unfold_ranges = Vec::new();
11714 let mut refold_creases = Vec::new();
11715
11716 let selections = self.selections.all::<Point>(&display_map);
11717 let mut selections = selections.iter().peekable();
11718 let mut contiguous_row_selections = Vec::new();
11719 let mut new_selections = Vec::new();
11720
11721 while let Some(selection) = selections.next() {
11722 // Find all the selections that span a contiguous row range
11723 let (start_row, end_row) = consume_contiguous_rows(
11724 &mut contiguous_row_selections,
11725 selection,
11726 &display_map,
11727 &mut selections,
11728 );
11729
11730 // Move the text spanned by the row range to be before the line preceding the row range
11731 if start_row.0 > 0 {
11732 let range_to_move = Point::new(
11733 start_row.previous_row().0,
11734 buffer.line_len(start_row.previous_row()),
11735 )
11736 ..Point::new(
11737 end_row.previous_row().0,
11738 buffer.line_len(end_row.previous_row()),
11739 );
11740 let insertion_point = display_map
11741 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11742 .0;
11743
11744 // Don't move lines across excerpts
11745 if buffer
11746 .excerpt_containing(insertion_point..range_to_move.end)
11747 .is_some()
11748 {
11749 let text = buffer
11750 .text_for_range(range_to_move.clone())
11751 .flat_map(|s| s.chars())
11752 .skip(1)
11753 .chain(['\n'])
11754 .collect::<String>();
11755
11756 edits.push((
11757 buffer.anchor_after(range_to_move.start)
11758 ..buffer.anchor_before(range_to_move.end),
11759 String::new(),
11760 ));
11761 let insertion_anchor = buffer.anchor_after(insertion_point);
11762 edits.push((insertion_anchor..insertion_anchor, text));
11763
11764 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11765
11766 // Move selections up
11767 new_selections.extend(contiguous_row_selections.drain(..).map(
11768 |mut selection| {
11769 selection.start.row -= row_delta;
11770 selection.end.row -= row_delta;
11771 selection
11772 },
11773 ));
11774
11775 // Move folds up
11776 unfold_ranges.push(range_to_move.clone());
11777 for fold in display_map.folds_in_range(
11778 buffer.anchor_before(range_to_move.start)
11779 ..buffer.anchor_after(range_to_move.end),
11780 ) {
11781 let mut start = fold.range.start.to_point(&buffer);
11782 let mut end = fold.range.end.to_point(&buffer);
11783 start.row -= row_delta;
11784 end.row -= row_delta;
11785 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11786 }
11787 }
11788 }
11789
11790 // If we didn't move line(s), preserve the existing selections
11791 new_selections.append(&mut contiguous_row_selections);
11792 }
11793
11794 self.transact(window, cx, |this, window, cx| {
11795 this.unfold_ranges(&unfold_ranges, true, true, cx);
11796 this.buffer.update(cx, |buffer, cx| {
11797 for (range, text) in edits {
11798 buffer.edit([(range, text)], None, cx);
11799 }
11800 });
11801 this.fold_creases(refold_creases, true, window, cx);
11802 this.change_selections(Default::default(), window, cx, |s| {
11803 s.select(new_selections);
11804 })
11805 });
11806 }
11807
11808 pub fn move_line_down(
11809 &mut self,
11810 _: &MoveLineDown,
11811 window: &mut Window,
11812 cx: &mut Context<Self>,
11813 ) {
11814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11815 if self.mode.is_single_line() {
11816 cx.propagate();
11817 return;
11818 }
11819
11820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11821 let buffer = self.buffer.read(cx).snapshot(cx);
11822
11823 let mut edits = Vec::new();
11824 let mut unfold_ranges = Vec::new();
11825 let mut refold_creases = Vec::new();
11826
11827 let selections = self.selections.all::<Point>(&display_map);
11828 let mut selections = selections.iter().peekable();
11829 let mut contiguous_row_selections = Vec::new();
11830 let mut new_selections = Vec::new();
11831
11832 while let Some(selection) = selections.next() {
11833 // Find all the selections that span a contiguous row range
11834 let (start_row, end_row) = consume_contiguous_rows(
11835 &mut contiguous_row_selections,
11836 selection,
11837 &display_map,
11838 &mut selections,
11839 );
11840
11841 // Move the text spanned by the row range to be after the last line of the row range
11842 if end_row.0 <= buffer.max_point().row {
11843 let range_to_move =
11844 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11845 let insertion_point = display_map
11846 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11847 .0;
11848
11849 // Don't move lines across excerpt boundaries
11850 if buffer
11851 .excerpt_containing(range_to_move.start..insertion_point)
11852 .is_some()
11853 {
11854 let mut text = String::from("\n");
11855 text.extend(buffer.text_for_range(range_to_move.clone()));
11856 text.pop(); // Drop trailing newline
11857 edits.push((
11858 buffer.anchor_after(range_to_move.start)
11859 ..buffer.anchor_before(range_to_move.end),
11860 String::new(),
11861 ));
11862 let insertion_anchor = buffer.anchor_after(insertion_point);
11863 edits.push((insertion_anchor..insertion_anchor, text));
11864
11865 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11866
11867 // Move selections down
11868 new_selections.extend(contiguous_row_selections.drain(..).map(
11869 |mut selection| {
11870 selection.start.row += row_delta;
11871 selection.end.row += row_delta;
11872 selection
11873 },
11874 ));
11875
11876 // Move folds down
11877 unfold_ranges.push(range_to_move.clone());
11878 for fold in display_map.folds_in_range(
11879 buffer.anchor_before(range_to_move.start)
11880 ..buffer.anchor_after(range_to_move.end),
11881 ) {
11882 let mut start = fold.range.start.to_point(&buffer);
11883 let mut end = fold.range.end.to_point(&buffer);
11884 start.row += row_delta;
11885 end.row += row_delta;
11886 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11887 }
11888 }
11889 }
11890
11891 // If we didn't move line(s), preserve the existing selections
11892 new_selections.append(&mut contiguous_row_selections);
11893 }
11894
11895 self.transact(window, cx, |this, window, cx| {
11896 this.unfold_ranges(&unfold_ranges, true, true, cx);
11897 this.buffer.update(cx, |buffer, cx| {
11898 for (range, text) in edits {
11899 buffer.edit([(range, text)], None, cx);
11900 }
11901 });
11902 this.fold_creases(refold_creases, true, window, cx);
11903 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11904 });
11905 }
11906
11907 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11909 let text_layout_details = &self.text_layout_details(window);
11910 self.transact(window, cx, |this, window, cx| {
11911 let edits = this.change_selections(Default::default(), window, cx, |s| {
11912 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11913 s.move_with(|display_map, selection| {
11914 if !selection.is_empty() {
11915 return;
11916 }
11917
11918 let mut head = selection.head();
11919 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11920 if head.column() == display_map.line_len(head.row()) {
11921 transpose_offset = display_map
11922 .buffer_snapshot()
11923 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11924 }
11925
11926 if transpose_offset == 0 {
11927 return;
11928 }
11929
11930 *head.column_mut() += 1;
11931 head = display_map.clip_point(head, Bias::Right);
11932 let goal = SelectionGoal::HorizontalPosition(
11933 display_map
11934 .x_for_display_point(head, text_layout_details)
11935 .into(),
11936 );
11937 selection.collapse_to(head, goal);
11938
11939 let transpose_start = display_map
11940 .buffer_snapshot()
11941 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11942 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11943 let transpose_end = display_map
11944 .buffer_snapshot()
11945 .clip_offset(transpose_offset + 1, Bias::Right);
11946 if let Some(ch) = display_map
11947 .buffer_snapshot()
11948 .chars_at(transpose_start)
11949 .next()
11950 {
11951 edits.push((transpose_start..transpose_offset, String::new()));
11952 edits.push((transpose_end..transpose_end, ch.to_string()));
11953 }
11954 }
11955 });
11956 edits
11957 });
11958 this.buffer
11959 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11960 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11961 this.change_selections(Default::default(), window, cx, |s| {
11962 s.select(selections);
11963 });
11964 });
11965 }
11966
11967 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11968 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11969 if self.mode.is_single_line() {
11970 cx.propagate();
11971 return;
11972 }
11973
11974 self.rewrap_impl(RewrapOptions::default(), cx)
11975 }
11976
11977 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11978 let buffer = self.buffer.read(cx).snapshot(cx);
11979 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11980
11981 #[derive(Clone, Debug, PartialEq)]
11982 enum CommentFormat {
11983 /// single line comment, with prefix for line
11984 Line(String),
11985 /// single line within a block comment, with prefix for line
11986 BlockLine(String),
11987 /// a single line of a block comment that includes the initial delimiter
11988 BlockCommentWithStart(BlockCommentConfig),
11989 /// a single line of a block comment that includes the ending delimiter
11990 BlockCommentWithEnd(BlockCommentConfig),
11991 }
11992
11993 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11994 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11995 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11996 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11997 .peekable();
11998
11999 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12000 row
12001 } else {
12002 return Vec::new();
12003 };
12004
12005 let language_settings = buffer.language_settings_at(selection.head(), cx);
12006 let language_scope = buffer.language_scope_at(selection.head());
12007
12008 let indent_and_prefix_for_row =
12009 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12010 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12011 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12012 &language_scope
12013 {
12014 let indent_end = Point::new(row, indent.len);
12015 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12016 let line_text_after_indent = buffer
12017 .text_for_range(indent_end..line_end)
12018 .collect::<String>();
12019
12020 let is_within_comment_override = buffer
12021 .language_scope_at(indent_end)
12022 .is_some_and(|scope| scope.override_name() == Some("comment"));
12023 let comment_delimiters = if is_within_comment_override {
12024 // we are within a comment syntax node, but we don't
12025 // yet know what kind of comment: block, doc or line
12026 match (
12027 language_scope.documentation_comment(),
12028 language_scope.block_comment(),
12029 ) {
12030 (Some(config), _) | (_, Some(config))
12031 if buffer.contains_str_at(indent_end, &config.start) =>
12032 {
12033 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12034 }
12035 (Some(config), _) | (_, Some(config))
12036 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12037 {
12038 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12039 }
12040 (Some(config), _) | (_, Some(config))
12041 if buffer.contains_str_at(indent_end, &config.prefix) =>
12042 {
12043 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12044 }
12045 (_, _) => language_scope
12046 .line_comment_prefixes()
12047 .iter()
12048 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12049 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12050 }
12051 } else {
12052 // we not in an overridden comment node, but we may
12053 // be within a non-overridden line comment node
12054 language_scope
12055 .line_comment_prefixes()
12056 .iter()
12057 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12058 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12059 };
12060
12061 let rewrap_prefix = language_scope
12062 .rewrap_prefixes()
12063 .iter()
12064 .find_map(|prefix_regex| {
12065 prefix_regex.find(&line_text_after_indent).map(|mat| {
12066 if mat.start() == 0 {
12067 Some(mat.as_str().to_string())
12068 } else {
12069 None
12070 }
12071 })
12072 })
12073 .flatten();
12074 (comment_delimiters, rewrap_prefix)
12075 } else {
12076 (None, None)
12077 };
12078 (indent, comment_prefix, rewrap_prefix)
12079 };
12080
12081 let mut ranges = Vec::new();
12082 let from_empty_selection = selection.is_empty();
12083
12084 let mut current_range_start = first_row;
12085 let mut prev_row = first_row;
12086 let (
12087 mut current_range_indent,
12088 mut current_range_comment_delimiters,
12089 mut current_range_rewrap_prefix,
12090 ) = indent_and_prefix_for_row(first_row);
12091
12092 for row in non_blank_rows_iter.skip(1) {
12093 let has_paragraph_break = row > prev_row + 1;
12094
12095 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12096 indent_and_prefix_for_row(row);
12097
12098 let has_indent_change = row_indent != current_range_indent;
12099 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12100
12101 let has_boundary_change = has_comment_change
12102 || row_rewrap_prefix.is_some()
12103 || (has_indent_change && current_range_comment_delimiters.is_some());
12104
12105 if has_paragraph_break || has_boundary_change {
12106 ranges.push((
12107 language_settings.clone(),
12108 Point::new(current_range_start, 0)
12109 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12110 current_range_indent,
12111 current_range_comment_delimiters.clone(),
12112 current_range_rewrap_prefix.clone(),
12113 from_empty_selection,
12114 ));
12115 current_range_start = row;
12116 current_range_indent = row_indent;
12117 current_range_comment_delimiters = row_comment_delimiters;
12118 current_range_rewrap_prefix = row_rewrap_prefix;
12119 }
12120 prev_row = row;
12121 }
12122
12123 ranges.push((
12124 language_settings.clone(),
12125 Point::new(current_range_start, 0)
12126 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12127 current_range_indent,
12128 current_range_comment_delimiters,
12129 current_range_rewrap_prefix,
12130 from_empty_selection,
12131 ));
12132
12133 ranges
12134 });
12135
12136 let mut edits = Vec::new();
12137 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12138
12139 for (
12140 language_settings,
12141 wrap_range,
12142 mut indent_size,
12143 comment_prefix,
12144 rewrap_prefix,
12145 from_empty_selection,
12146 ) in wrap_ranges
12147 {
12148 let mut start_row = wrap_range.start.row;
12149 let mut end_row = wrap_range.end.row;
12150
12151 // Skip selections that overlap with a range that has already been rewrapped.
12152 let selection_range = start_row..end_row;
12153 if rewrapped_row_ranges
12154 .iter()
12155 .any(|range| range.overlaps(&selection_range))
12156 {
12157 continue;
12158 }
12159
12160 let tab_size = language_settings.tab_size;
12161
12162 let (line_prefix, inside_comment) = match &comment_prefix {
12163 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12164 (Some(prefix.as_str()), true)
12165 }
12166 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12167 (Some(prefix.as_ref()), true)
12168 }
12169 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12170 start: _,
12171 end: _,
12172 prefix,
12173 tab_size,
12174 })) => {
12175 indent_size.len += tab_size;
12176 (Some(prefix.as_ref()), true)
12177 }
12178 None => (None, false),
12179 };
12180 let indent_prefix = indent_size.chars().collect::<String>();
12181 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12182
12183 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12184 RewrapBehavior::InComments => inside_comment,
12185 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12186 RewrapBehavior::Anywhere => true,
12187 };
12188
12189 let should_rewrap = options.override_language_settings
12190 || allow_rewrap_based_on_language
12191 || self.hard_wrap.is_some();
12192 if !should_rewrap {
12193 continue;
12194 }
12195
12196 if from_empty_selection {
12197 'expand_upwards: while start_row > 0 {
12198 let prev_row = start_row - 1;
12199 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12200 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12201 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12202 {
12203 start_row = prev_row;
12204 } else {
12205 break 'expand_upwards;
12206 }
12207 }
12208
12209 'expand_downwards: while end_row < buffer.max_point().row {
12210 let next_row = end_row + 1;
12211 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12212 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12213 && !buffer.is_line_blank(MultiBufferRow(next_row))
12214 {
12215 end_row = next_row;
12216 } else {
12217 break 'expand_downwards;
12218 }
12219 }
12220 }
12221
12222 let start = Point::new(start_row, 0);
12223 let start_offset = ToOffset::to_offset(&start, &buffer);
12224 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12225 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12226 let mut first_line_delimiter = None;
12227 let mut last_line_delimiter = None;
12228 let Some(lines_without_prefixes) = selection_text
12229 .lines()
12230 .enumerate()
12231 .map(|(ix, line)| {
12232 let line_trimmed = line.trim_start();
12233 if rewrap_prefix.is_some() && ix > 0 {
12234 Ok(line_trimmed)
12235 } else if let Some(
12236 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12237 start,
12238 prefix,
12239 end,
12240 tab_size,
12241 })
12242 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12243 start,
12244 prefix,
12245 end,
12246 tab_size,
12247 }),
12248 ) = &comment_prefix
12249 {
12250 let line_trimmed = line_trimmed
12251 .strip_prefix(start.as_ref())
12252 .map(|s| {
12253 let mut indent_size = indent_size;
12254 indent_size.len -= tab_size;
12255 let indent_prefix: String = indent_size.chars().collect();
12256 first_line_delimiter = Some((indent_prefix, start));
12257 s.trim_start()
12258 })
12259 .unwrap_or(line_trimmed);
12260 let line_trimmed = line_trimmed
12261 .strip_suffix(end.as_ref())
12262 .map(|s| {
12263 last_line_delimiter = Some(end);
12264 s.trim_end()
12265 })
12266 .unwrap_or(line_trimmed);
12267 let line_trimmed = line_trimmed
12268 .strip_prefix(prefix.as_ref())
12269 .unwrap_or(line_trimmed);
12270 Ok(line_trimmed)
12271 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12272 line_trimmed.strip_prefix(prefix).with_context(|| {
12273 format!("line did not start with prefix {prefix:?}: {line:?}")
12274 })
12275 } else {
12276 line_trimmed
12277 .strip_prefix(&line_prefix.trim_start())
12278 .with_context(|| {
12279 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12280 })
12281 }
12282 })
12283 .collect::<Result<Vec<_>, _>>()
12284 .log_err()
12285 else {
12286 continue;
12287 };
12288
12289 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12290 buffer
12291 .language_settings_at(Point::new(start_row, 0), cx)
12292 .preferred_line_length as usize
12293 });
12294
12295 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12296 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12297 } else {
12298 line_prefix.clone()
12299 };
12300
12301 let wrapped_text = {
12302 let mut wrapped_text = wrap_with_prefix(
12303 line_prefix,
12304 subsequent_lines_prefix,
12305 lines_without_prefixes.join("\n"),
12306 wrap_column,
12307 tab_size,
12308 options.preserve_existing_whitespace,
12309 );
12310
12311 if let Some((indent, delimiter)) = first_line_delimiter {
12312 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12313 }
12314 if let Some(last_line) = last_line_delimiter {
12315 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12316 }
12317
12318 wrapped_text
12319 };
12320
12321 // TODO: should always use char-based diff while still supporting cursor behavior that
12322 // matches vim.
12323 let mut diff_options = DiffOptions::default();
12324 if options.override_language_settings {
12325 diff_options.max_word_diff_len = 0;
12326 diff_options.max_word_diff_line_count = 0;
12327 } else {
12328 diff_options.max_word_diff_len = usize::MAX;
12329 diff_options.max_word_diff_line_count = usize::MAX;
12330 }
12331
12332 for (old_range, new_text) in
12333 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12334 {
12335 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12336 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12337 edits.push((edit_start..edit_end, new_text));
12338 }
12339
12340 rewrapped_row_ranges.push(start_row..=end_row);
12341 }
12342
12343 self.buffer
12344 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12345 }
12346
12347 pub fn cut_common(
12348 &mut self,
12349 cut_no_selection_line: bool,
12350 window: &mut Window,
12351 cx: &mut Context<Self>,
12352 ) -> ClipboardItem {
12353 let mut text = String::new();
12354 let buffer = self.buffer.read(cx).snapshot(cx);
12355 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12356 let mut clipboard_selections = Vec::with_capacity(selections.len());
12357 {
12358 let max_point = buffer.max_point();
12359 let mut is_first = true;
12360 for selection in &mut selections {
12361 let is_entire_line =
12362 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12363 if is_entire_line {
12364 selection.start = Point::new(selection.start.row, 0);
12365 if !selection.is_empty() && selection.end.column == 0 {
12366 selection.end = cmp::min(max_point, selection.end);
12367 } else {
12368 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12369 }
12370 selection.goal = SelectionGoal::None;
12371 }
12372 if is_first {
12373 is_first = false;
12374 } else {
12375 text += "\n";
12376 }
12377 let mut len = 0;
12378 for chunk in buffer.text_for_range(selection.start..selection.end) {
12379 text.push_str(chunk);
12380 len += chunk.len();
12381 }
12382 clipboard_selections.push(ClipboardSelection {
12383 len,
12384 is_entire_line,
12385 first_line_indent: buffer
12386 .indent_size_for_line(MultiBufferRow(selection.start.row))
12387 .len,
12388 });
12389 }
12390 }
12391
12392 self.transact(window, cx, |this, window, cx| {
12393 this.change_selections(Default::default(), window, cx, |s| {
12394 s.select(selections);
12395 });
12396 this.insert("", window, cx);
12397 });
12398 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12399 }
12400
12401 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12403 let item = self.cut_common(true, window, cx);
12404 cx.write_to_clipboard(item);
12405 }
12406
12407 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12408 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12409 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12410 s.move_with(|snapshot, sel| {
12411 if sel.is_empty() {
12412 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12413 }
12414 if sel.is_empty() {
12415 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12416 }
12417 });
12418 });
12419 let item = self.cut_common(false, window, cx);
12420 cx.set_global(KillRing(item))
12421 }
12422
12423 pub fn kill_ring_yank(
12424 &mut self,
12425 _: &KillRingYank,
12426 window: &mut Window,
12427 cx: &mut Context<Self>,
12428 ) {
12429 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12430 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12431 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12432 (kill_ring.text().to_string(), kill_ring.metadata_json())
12433 } else {
12434 return;
12435 }
12436 } else {
12437 return;
12438 };
12439 self.do_paste(&text, metadata, false, window, cx);
12440 }
12441
12442 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12443 self.do_copy(true, cx);
12444 }
12445
12446 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12447 self.do_copy(false, cx);
12448 }
12449
12450 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12451 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12452 let buffer = self.buffer.read(cx).read(cx);
12453 let mut text = String::new();
12454
12455 let mut clipboard_selections = Vec::with_capacity(selections.len());
12456 {
12457 let max_point = buffer.max_point();
12458 let mut is_first = true;
12459 for selection in &selections {
12460 let mut start = selection.start;
12461 let mut end = selection.end;
12462 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12463 let mut add_trailing_newline = false;
12464 if is_entire_line {
12465 start = Point::new(start.row, 0);
12466 let next_line_start = Point::new(end.row + 1, 0);
12467 if next_line_start <= max_point {
12468 end = next_line_start;
12469 } else {
12470 // We're on the last line without a trailing newline.
12471 // Copy to the end of the line and add a newline afterwards.
12472 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12473 add_trailing_newline = true;
12474 }
12475 }
12476
12477 let mut trimmed_selections = Vec::new();
12478 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12479 let row = MultiBufferRow(start.row);
12480 let first_indent = buffer.indent_size_for_line(row);
12481 if first_indent.len == 0 || start.column > first_indent.len {
12482 trimmed_selections.push(start..end);
12483 } else {
12484 trimmed_selections.push(
12485 Point::new(row.0, first_indent.len)
12486 ..Point::new(row.0, buffer.line_len(row)),
12487 );
12488 for row in start.row + 1..=end.row {
12489 let mut line_len = buffer.line_len(MultiBufferRow(row));
12490 if row == end.row {
12491 line_len = end.column;
12492 }
12493 if line_len == 0 {
12494 trimmed_selections
12495 .push(Point::new(row, 0)..Point::new(row, line_len));
12496 continue;
12497 }
12498 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12499 if row_indent_size.len >= first_indent.len {
12500 trimmed_selections.push(
12501 Point::new(row, first_indent.len)..Point::new(row, line_len),
12502 );
12503 } else {
12504 trimmed_selections.clear();
12505 trimmed_selections.push(start..end);
12506 break;
12507 }
12508 }
12509 }
12510 } else {
12511 trimmed_selections.push(start..end);
12512 }
12513
12514 for trimmed_range in trimmed_selections {
12515 if is_first {
12516 is_first = false;
12517 } else {
12518 text += "\n";
12519 }
12520 let mut len = 0;
12521 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12522 text.push_str(chunk);
12523 len += chunk.len();
12524 }
12525 if add_trailing_newline {
12526 text.push('\n');
12527 len += 1;
12528 }
12529 clipboard_selections.push(ClipboardSelection {
12530 len,
12531 is_entire_line,
12532 first_line_indent: buffer
12533 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12534 .len,
12535 });
12536 }
12537 }
12538 }
12539
12540 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12541 text,
12542 clipboard_selections,
12543 ));
12544 }
12545
12546 pub fn do_paste(
12547 &mut self,
12548 text: &String,
12549 clipboard_selections: Option<Vec<ClipboardSelection>>,
12550 handle_entire_lines: bool,
12551 window: &mut Window,
12552 cx: &mut Context<Self>,
12553 ) {
12554 if self.read_only(cx) {
12555 return;
12556 }
12557
12558 let clipboard_text = Cow::Borrowed(text.as_str());
12559
12560 self.transact(window, cx, |this, window, cx| {
12561 let had_active_edit_prediction = this.has_active_edit_prediction();
12562 let display_map = this.display_snapshot(cx);
12563 let old_selections = this.selections.all::<usize>(&display_map);
12564 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12565
12566 if let Some(mut clipboard_selections) = clipboard_selections {
12567 let all_selections_were_entire_line =
12568 clipboard_selections.iter().all(|s| s.is_entire_line);
12569 let first_selection_indent_column =
12570 clipboard_selections.first().map(|s| s.first_line_indent);
12571 if clipboard_selections.len() != old_selections.len() {
12572 clipboard_selections.drain(..);
12573 }
12574 let mut auto_indent_on_paste = true;
12575
12576 this.buffer.update(cx, |buffer, cx| {
12577 let snapshot = buffer.read(cx);
12578 auto_indent_on_paste = snapshot
12579 .language_settings_at(cursor_offset, cx)
12580 .auto_indent_on_paste;
12581
12582 let mut start_offset = 0;
12583 let mut edits = Vec::new();
12584 let mut original_indent_columns = Vec::new();
12585 for (ix, selection) in old_selections.iter().enumerate() {
12586 let to_insert;
12587 let entire_line;
12588 let original_indent_column;
12589 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12590 let end_offset = start_offset + clipboard_selection.len;
12591 to_insert = &clipboard_text[start_offset..end_offset];
12592 entire_line = clipboard_selection.is_entire_line;
12593 start_offset = end_offset + 1;
12594 original_indent_column = Some(clipboard_selection.first_line_indent);
12595 } else {
12596 to_insert = &*clipboard_text;
12597 entire_line = all_selections_were_entire_line;
12598 original_indent_column = first_selection_indent_column
12599 }
12600
12601 let (range, to_insert) =
12602 if selection.is_empty() && handle_entire_lines && entire_line {
12603 // If the corresponding selection was empty when this slice of the
12604 // clipboard text was written, then the entire line containing the
12605 // selection was copied. If this selection is also currently empty,
12606 // then paste the line before the current line of the buffer.
12607 let column = selection.start.to_point(&snapshot).column as usize;
12608 let line_start = selection.start - column;
12609 (line_start..line_start, Cow::Borrowed(to_insert))
12610 } else {
12611 let language = snapshot.language_at(selection.head());
12612 let range = selection.range();
12613 if let Some(language) = language
12614 && language.name() == "Markdown".into()
12615 {
12616 edit_for_markdown_paste(
12617 &snapshot,
12618 range,
12619 to_insert,
12620 url::Url::parse(to_insert).ok(),
12621 )
12622 } else {
12623 (range, Cow::Borrowed(to_insert))
12624 }
12625 };
12626
12627 edits.push((range, to_insert));
12628 original_indent_columns.push(original_indent_column);
12629 }
12630 drop(snapshot);
12631
12632 buffer.edit(
12633 edits,
12634 if auto_indent_on_paste {
12635 Some(AutoindentMode::Block {
12636 original_indent_columns,
12637 })
12638 } else {
12639 None
12640 },
12641 cx,
12642 );
12643 });
12644
12645 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12646 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12647 } else {
12648 let url = url::Url::parse(&clipboard_text).ok();
12649
12650 let auto_indent_mode = if !clipboard_text.is_empty() {
12651 Some(AutoindentMode::Block {
12652 original_indent_columns: Vec::new(),
12653 })
12654 } else {
12655 None
12656 };
12657
12658 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12659 let snapshot = buffer.snapshot(cx);
12660
12661 let anchors = old_selections
12662 .iter()
12663 .map(|s| {
12664 let anchor = snapshot.anchor_after(s.head());
12665 s.map(|_| anchor)
12666 })
12667 .collect::<Vec<_>>();
12668
12669 let mut edits = Vec::new();
12670
12671 for selection in old_selections.iter() {
12672 let language = snapshot.language_at(selection.head());
12673 let range = selection.range();
12674
12675 let (edit_range, edit_text) = if let Some(language) = language
12676 && language.name() == "Markdown".into()
12677 {
12678 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12679 } else {
12680 (range, clipboard_text.clone())
12681 };
12682
12683 edits.push((edit_range, edit_text));
12684 }
12685
12686 drop(snapshot);
12687 buffer.edit(edits, auto_indent_mode, cx);
12688
12689 anchors
12690 });
12691
12692 this.change_selections(Default::default(), window, cx, |s| {
12693 s.select_anchors(selection_anchors);
12694 });
12695 }
12696
12697 let trigger_in_words =
12698 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12699
12700 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12701 });
12702 }
12703
12704 pub fn diff_clipboard_with_selection(
12705 &mut self,
12706 _: &DiffClipboardWithSelection,
12707 window: &mut Window,
12708 cx: &mut Context<Self>,
12709 ) {
12710 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12711
12712 if selections.is_empty() {
12713 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12714 return;
12715 };
12716
12717 let clipboard_text = match cx.read_from_clipboard() {
12718 Some(item) => match item.entries().first() {
12719 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12720 _ => None,
12721 },
12722 None => None,
12723 };
12724
12725 let Some(clipboard_text) = clipboard_text else {
12726 log::warn!("Clipboard doesn't contain text.");
12727 return;
12728 };
12729
12730 window.dispatch_action(
12731 Box::new(DiffClipboardWithSelectionData {
12732 clipboard_text,
12733 editor: cx.entity(),
12734 }),
12735 cx,
12736 );
12737 }
12738
12739 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12741 if let Some(item) = cx.read_from_clipboard() {
12742 let entries = item.entries();
12743
12744 match entries.first() {
12745 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12746 // of all the pasted entries.
12747 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12748 .do_paste(
12749 clipboard_string.text(),
12750 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12751 true,
12752 window,
12753 cx,
12754 ),
12755 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12756 }
12757 }
12758 }
12759
12760 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12761 if self.read_only(cx) {
12762 return;
12763 }
12764
12765 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12766
12767 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12768 if let Some((selections, _)) =
12769 self.selection_history.transaction(transaction_id).cloned()
12770 {
12771 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12772 s.select_anchors(selections.to_vec());
12773 });
12774 } else {
12775 log::error!(
12776 "No entry in selection_history found for undo. \
12777 This may correspond to a bug where undo does not update the selection. \
12778 If this is occurring, please add details to \
12779 https://github.com/zed-industries/zed/issues/22692"
12780 );
12781 }
12782 self.request_autoscroll(Autoscroll::fit(), cx);
12783 self.unmark_text(window, cx);
12784 self.refresh_edit_prediction(true, false, window, cx);
12785 cx.emit(EditorEvent::Edited { transaction_id });
12786 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12787 }
12788 }
12789
12790 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12791 if self.read_only(cx) {
12792 return;
12793 }
12794
12795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12796
12797 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12798 if let Some((_, Some(selections))) =
12799 self.selection_history.transaction(transaction_id).cloned()
12800 {
12801 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12802 s.select_anchors(selections.to_vec());
12803 });
12804 } else {
12805 log::error!(
12806 "No entry in selection_history found for redo. \
12807 This may correspond to a bug where undo does not update the selection. \
12808 If this is occurring, please add details to \
12809 https://github.com/zed-industries/zed/issues/22692"
12810 );
12811 }
12812 self.request_autoscroll(Autoscroll::fit(), cx);
12813 self.unmark_text(window, cx);
12814 self.refresh_edit_prediction(true, false, window, cx);
12815 cx.emit(EditorEvent::Edited { transaction_id });
12816 }
12817 }
12818
12819 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12820 self.buffer
12821 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12822 }
12823
12824 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12825 self.buffer
12826 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12827 }
12828
12829 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12831 self.change_selections(Default::default(), window, cx, |s| {
12832 s.move_with(|map, selection| {
12833 let cursor = if selection.is_empty() {
12834 movement::left(map, selection.start)
12835 } else {
12836 selection.start
12837 };
12838 selection.collapse_to(cursor, SelectionGoal::None);
12839 });
12840 })
12841 }
12842
12843 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12845 self.change_selections(Default::default(), window, cx, |s| {
12846 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12847 })
12848 }
12849
12850 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12852 self.change_selections(Default::default(), window, cx, |s| {
12853 s.move_with(|map, selection| {
12854 let cursor = if selection.is_empty() {
12855 movement::right(map, selection.end)
12856 } else {
12857 selection.end
12858 };
12859 selection.collapse_to(cursor, SelectionGoal::None)
12860 });
12861 })
12862 }
12863
12864 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12866 self.change_selections(Default::default(), window, cx, |s| {
12867 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12868 });
12869 }
12870
12871 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12872 if self.take_rename(true, window, cx).is_some() {
12873 return;
12874 }
12875
12876 if self.mode.is_single_line() {
12877 cx.propagate();
12878 return;
12879 }
12880
12881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12882
12883 let text_layout_details = &self.text_layout_details(window);
12884 let selection_count = self.selections.count();
12885 let first_selection = self.selections.first_anchor();
12886
12887 self.change_selections(Default::default(), window, cx, |s| {
12888 s.move_with(|map, selection| {
12889 if !selection.is_empty() {
12890 selection.goal = SelectionGoal::None;
12891 }
12892 let (cursor, goal) = movement::up(
12893 map,
12894 selection.start,
12895 selection.goal,
12896 false,
12897 text_layout_details,
12898 );
12899 selection.collapse_to(cursor, goal);
12900 });
12901 });
12902
12903 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12904 {
12905 cx.propagate();
12906 }
12907 }
12908
12909 pub fn move_up_by_lines(
12910 &mut self,
12911 action: &MoveUpByLines,
12912 window: &mut Window,
12913 cx: &mut Context<Self>,
12914 ) {
12915 if self.take_rename(true, window, cx).is_some() {
12916 return;
12917 }
12918
12919 if self.mode.is_single_line() {
12920 cx.propagate();
12921 return;
12922 }
12923
12924 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12925
12926 let text_layout_details = &self.text_layout_details(window);
12927
12928 self.change_selections(Default::default(), window, cx, |s| {
12929 s.move_with(|map, selection| {
12930 if !selection.is_empty() {
12931 selection.goal = SelectionGoal::None;
12932 }
12933 let (cursor, goal) = movement::up_by_rows(
12934 map,
12935 selection.start,
12936 action.lines,
12937 selection.goal,
12938 false,
12939 text_layout_details,
12940 );
12941 selection.collapse_to(cursor, goal);
12942 });
12943 })
12944 }
12945
12946 pub fn move_down_by_lines(
12947 &mut self,
12948 action: &MoveDownByLines,
12949 window: &mut Window,
12950 cx: &mut Context<Self>,
12951 ) {
12952 if self.take_rename(true, window, cx).is_some() {
12953 return;
12954 }
12955
12956 if self.mode.is_single_line() {
12957 cx.propagate();
12958 return;
12959 }
12960
12961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12962
12963 let text_layout_details = &self.text_layout_details(window);
12964
12965 self.change_selections(Default::default(), window, cx, |s| {
12966 s.move_with(|map, selection| {
12967 if !selection.is_empty() {
12968 selection.goal = SelectionGoal::None;
12969 }
12970 let (cursor, goal) = movement::down_by_rows(
12971 map,
12972 selection.start,
12973 action.lines,
12974 selection.goal,
12975 false,
12976 text_layout_details,
12977 );
12978 selection.collapse_to(cursor, goal);
12979 });
12980 })
12981 }
12982
12983 pub fn select_down_by_lines(
12984 &mut self,
12985 action: &SelectDownByLines,
12986 window: &mut Window,
12987 cx: &mut Context<Self>,
12988 ) {
12989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12990 let text_layout_details = &self.text_layout_details(window);
12991 self.change_selections(Default::default(), window, cx, |s| {
12992 s.move_heads_with(|map, head, goal| {
12993 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12994 })
12995 })
12996 }
12997
12998 pub fn select_up_by_lines(
12999 &mut self,
13000 action: &SelectUpByLines,
13001 window: &mut Window,
13002 cx: &mut Context<Self>,
13003 ) {
13004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13005 let text_layout_details = &self.text_layout_details(window);
13006 self.change_selections(Default::default(), window, cx, |s| {
13007 s.move_heads_with(|map, head, goal| {
13008 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13009 })
13010 })
13011 }
13012
13013 pub fn select_page_up(
13014 &mut self,
13015 _: &SelectPageUp,
13016 window: &mut Window,
13017 cx: &mut Context<Self>,
13018 ) {
13019 let Some(row_count) = self.visible_row_count() else {
13020 return;
13021 };
13022
13023 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13024
13025 let text_layout_details = &self.text_layout_details(window);
13026
13027 self.change_selections(Default::default(), window, cx, |s| {
13028 s.move_heads_with(|map, head, goal| {
13029 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13030 })
13031 })
13032 }
13033
13034 pub fn move_page_up(
13035 &mut self,
13036 action: &MovePageUp,
13037 window: &mut Window,
13038 cx: &mut Context<Self>,
13039 ) {
13040 if self.take_rename(true, window, cx).is_some() {
13041 return;
13042 }
13043
13044 if self
13045 .context_menu
13046 .borrow_mut()
13047 .as_mut()
13048 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13049 .unwrap_or(false)
13050 {
13051 return;
13052 }
13053
13054 if matches!(self.mode, EditorMode::SingleLine) {
13055 cx.propagate();
13056 return;
13057 }
13058
13059 let Some(row_count) = self.visible_row_count() else {
13060 return;
13061 };
13062
13063 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13064
13065 let effects = if action.center_cursor {
13066 SelectionEffects::scroll(Autoscroll::center())
13067 } else {
13068 SelectionEffects::default()
13069 };
13070
13071 let text_layout_details = &self.text_layout_details(window);
13072
13073 self.change_selections(effects, window, cx, |s| {
13074 s.move_with(|map, selection| {
13075 if !selection.is_empty() {
13076 selection.goal = SelectionGoal::None;
13077 }
13078 let (cursor, goal) = movement::up_by_rows(
13079 map,
13080 selection.end,
13081 row_count,
13082 selection.goal,
13083 false,
13084 text_layout_details,
13085 );
13086 selection.collapse_to(cursor, goal);
13087 });
13088 });
13089 }
13090
13091 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13093 let text_layout_details = &self.text_layout_details(window);
13094 self.change_selections(Default::default(), window, cx, |s| {
13095 s.move_heads_with(|map, head, goal| {
13096 movement::up(map, head, goal, false, text_layout_details)
13097 })
13098 })
13099 }
13100
13101 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13102 self.take_rename(true, window, cx);
13103
13104 if self.mode.is_single_line() {
13105 cx.propagate();
13106 return;
13107 }
13108
13109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13110
13111 let text_layout_details = &self.text_layout_details(window);
13112 let selection_count = self.selections.count();
13113 let first_selection = self.selections.first_anchor();
13114
13115 self.change_selections(Default::default(), window, cx, |s| {
13116 s.move_with(|map, selection| {
13117 if !selection.is_empty() {
13118 selection.goal = SelectionGoal::None;
13119 }
13120 let (cursor, goal) = movement::down(
13121 map,
13122 selection.end,
13123 selection.goal,
13124 false,
13125 text_layout_details,
13126 );
13127 selection.collapse_to(cursor, goal);
13128 });
13129 });
13130
13131 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13132 {
13133 cx.propagate();
13134 }
13135 }
13136
13137 pub fn select_page_down(
13138 &mut self,
13139 _: &SelectPageDown,
13140 window: &mut Window,
13141 cx: &mut Context<Self>,
13142 ) {
13143 let Some(row_count) = self.visible_row_count() else {
13144 return;
13145 };
13146
13147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13148
13149 let text_layout_details = &self.text_layout_details(window);
13150
13151 self.change_selections(Default::default(), window, cx, |s| {
13152 s.move_heads_with(|map, head, goal| {
13153 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13154 })
13155 })
13156 }
13157
13158 pub fn move_page_down(
13159 &mut self,
13160 action: &MovePageDown,
13161 window: &mut Window,
13162 cx: &mut Context<Self>,
13163 ) {
13164 if self.take_rename(true, window, cx).is_some() {
13165 return;
13166 }
13167
13168 if self
13169 .context_menu
13170 .borrow_mut()
13171 .as_mut()
13172 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13173 .unwrap_or(false)
13174 {
13175 return;
13176 }
13177
13178 if matches!(self.mode, EditorMode::SingleLine) {
13179 cx.propagate();
13180 return;
13181 }
13182
13183 let Some(row_count) = self.visible_row_count() else {
13184 return;
13185 };
13186
13187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13188
13189 let effects = if action.center_cursor {
13190 SelectionEffects::scroll(Autoscroll::center())
13191 } else {
13192 SelectionEffects::default()
13193 };
13194
13195 let text_layout_details = &self.text_layout_details(window);
13196 self.change_selections(effects, window, cx, |s| {
13197 s.move_with(|map, selection| {
13198 if !selection.is_empty() {
13199 selection.goal = SelectionGoal::None;
13200 }
13201 let (cursor, goal) = movement::down_by_rows(
13202 map,
13203 selection.end,
13204 row_count,
13205 selection.goal,
13206 false,
13207 text_layout_details,
13208 );
13209 selection.collapse_to(cursor, goal);
13210 });
13211 });
13212 }
13213
13214 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13216 let text_layout_details = &self.text_layout_details(window);
13217 self.change_selections(Default::default(), window, cx, |s| {
13218 s.move_heads_with(|map, head, goal| {
13219 movement::down(map, head, goal, false, text_layout_details)
13220 })
13221 });
13222 }
13223
13224 pub fn context_menu_first(
13225 &mut self,
13226 _: &ContextMenuFirst,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13231 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13232 }
13233 }
13234
13235 pub fn context_menu_prev(
13236 &mut self,
13237 _: &ContextMenuPrevious,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) {
13241 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13242 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13243 }
13244 }
13245
13246 pub fn context_menu_next(
13247 &mut self,
13248 _: &ContextMenuNext,
13249 window: &mut Window,
13250 cx: &mut Context<Self>,
13251 ) {
13252 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13253 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13254 }
13255 }
13256
13257 pub fn context_menu_last(
13258 &mut self,
13259 _: &ContextMenuLast,
13260 window: &mut Window,
13261 cx: &mut Context<Self>,
13262 ) {
13263 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13264 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13265 }
13266 }
13267
13268 pub fn signature_help_prev(
13269 &mut self,
13270 _: &SignatureHelpPrevious,
13271 _: &mut Window,
13272 cx: &mut Context<Self>,
13273 ) {
13274 if let Some(popover) = self.signature_help_state.popover_mut() {
13275 if popover.current_signature == 0 {
13276 popover.current_signature = popover.signatures.len() - 1;
13277 } else {
13278 popover.current_signature -= 1;
13279 }
13280 cx.notify();
13281 }
13282 }
13283
13284 pub fn signature_help_next(
13285 &mut self,
13286 _: &SignatureHelpNext,
13287 _: &mut Window,
13288 cx: &mut Context<Self>,
13289 ) {
13290 if let Some(popover) = self.signature_help_state.popover_mut() {
13291 if popover.current_signature + 1 == popover.signatures.len() {
13292 popover.current_signature = 0;
13293 } else {
13294 popover.current_signature += 1;
13295 }
13296 cx.notify();
13297 }
13298 }
13299
13300 pub fn move_to_previous_word_start(
13301 &mut self,
13302 _: &MoveToPreviousWordStart,
13303 window: &mut Window,
13304 cx: &mut Context<Self>,
13305 ) {
13306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13307 self.change_selections(Default::default(), window, cx, |s| {
13308 s.move_cursors_with(|map, head, _| {
13309 (
13310 movement::previous_word_start(map, head),
13311 SelectionGoal::None,
13312 )
13313 });
13314 })
13315 }
13316
13317 pub fn move_to_previous_subword_start(
13318 &mut self,
13319 _: &MoveToPreviousSubwordStart,
13320 window: &mut Window,
13321 cx: &mut Context<Self>,
13322 ) {
13323 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13324 self.change_selections(Default::default(), window, cx, |s| {
13325 s.move_cursors_with(|map, head, _| {
13326 (
13327 movement::previous_subword_start(map, head),
13328 SelectionGoal::None,
13329 )
13330 });
13331 })
13332 }
13333
13334 pub fn select_to_previous_word_start(
13335 &mut self,
13336 _: &SelectToPreviousWordStart,
13337 window: &mut Window,
13338 cx: &mut Context<Self>,
13339 ) {
13340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13341 self.change_selections(Default::default(), window, cx, |s| {
13342 s.move_heads_with(|map, head, _| {
13343 (
13344 movement::previous_word_start(map, head),
13345 SelectionGoal::None,
13346 )
13347 });
13348 })
13349 }
13350
13351 pub fn select_to_previous_subword_start(
13352 &mut self,
13353 _: &SelectToPreviousSubwordStart,
13354 window: &mut Window,
13355 cx: &mut Context<Self>,
13356 ) {
13357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13358 self.change_selections(Default::default(), window, cx, |s| {
13359 s.move_heads_with(|map, head, _| {
13360 (
13361 movement::previous_subword_start(map, head),
13362 SelectionGoal::None,
13363 )
13364 });
13365 })
13366 }
13367
13368 pub fn delete_to_previous_word_start(
13369 &mut self,
13370 action: &DeleteToPreviousWordStart,
13371 window: &mut Window,
13372 cx: &mut Context<Self>,
13373 ) {
13374 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13375 self.transact(window, cx, |this, window, cx| {
13376 this.select_autoclose_pair(window, cx);
13377 this.change_selections(Default::default(), window, cx, |s| {
13378 s.move_with(|map, selection| {
13379 if selection.is_empty() {
13380 let mut cursor = if action.ignore_newlines {
13381 movement::previous_word_start(map, selection.head())
13382 } else {
13383 movement::previous_word_start_or_newline(map, selection.head())
13384 };
13385 cursor = movement::adjust_greedy_deletion(
13386 map,
13387 selection.head(),
13388 cursor,
13389 action.ignore_brackets,
13390 );
13391 selection.set_head(cursor, SelectionGoal::None);
13392 }
13393 });
13394 });
13395 this.insert("", window, cx);
13396 });
13397 }
13398
13399 pub fn delete_to_previous_subword_start(
13400 &mut self,
13401 _: &DeleteToPreviousSubwordStart,
13402 window: &mut Window,
13403 cx: &mut Context<Self>,
13404 ) {
13405 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13406 self.transact(window, cx, |this, window, cx| {
13407 this.select_autoclose_pair(window, cx);
13408 this.change_selections(Default::default(), window, cx, |s| {
13409 s.move_with(|map, selection| {
13410 if selection.is_empty() {
13411 let mut cursor = movement::previous_subword_start(map, selection.head());
13412 cursor =
13413 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13414 selection.set_head(cursor, SelectionGoal::None);
13415 }
13416 });
13417 });
13418 this.insert("", window, cx);
13419 });
13420 }
13421
13422 pub fn move_to_next_word_end(
13423 &mut self,
13424 _: &MoveToNextWordEnd,
13425 window: &mut Window,
13426 cx: &mut Context<Self>,
13427 ) {
13428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13429 self.change_selections(Default::default(), window, cx, |s| {
13430 s.move_cursors_with(|map, head, _| {
13431 (movement::next_word_end(map, head), SelectionGoal::None)
13432 });
13433 })
13434 }
13435
13436 pub fn move_to_next_subword_end(
13437 &mut self,
13438 _: &MoveToNextSubwordEnd,
13439 window: &mut Window,
13440 cx: &mut Context<Self>,
13441 ) {
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13443 self.change_selections(Default::default(), window, cx, |s| {
13444 s.move_cursors_with(|map, head, _| {
13445 (movement::next_subword_end(map, head), SelectionGoal::None)
13446 });
13447 })
13448 }
13449
13450 pub fn select_to_next_word_end(
13451 &mut self,
13452 _: &SelectToNextWordEnd,
13453 window: &mut Window,
13454 cx: &mut Context<Self>,
13455 ) {
13456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13457 self.change_selections(Default::default(), window, cx, |s| {
13458 s.move_heads_with(|map, head, _| {
13459 (movement::next_word_end(map, head), SelectionGoal::None)
13460 });
13461 })
13462 }
13463
13464 pub fn select_to_next_subword_end(
13465 &mut self,
13466 _: &SelectToNextSubwordEnd,
13467 window: &mut Window,
13468 cx: &mut Context<Self>,
13469 ) {
13470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13471 self.change_selections(Default::default(), window, cx, |s| {
13472 s.move_heads_with(|map, head, _| {
13473 (movement::next_subword_end(map, head), SelectionGoal::None)
13474 });
13475 })
13476 }
13477
13478 pub fn delete_to_next_word_end(
13479 &mut self,
13480 action: &DeleteToNextWordEnd,
13481 window: &mut Window,
13482 cx: &mut Context<Self>,
13483 ) {
13484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13485 self.transact(window, cx, |this, window, cx| {
13486 this.change_selections(Default::default(), window, cx, |s| {
13487 s.move_with(|map, selection| {
13488 if selection.is_empty() {
13489 let mut cursor = if action.ignore_newlines {
13490 movement::next_word_end(map, selection.head())
13491 } else {
13492 movement::next_word_end_or_newline(map, selection.head())
13493 };
13494 cursor = movement::adjust_greedy_deletion(
13495 map,
13496 selection.head(),
13497 cursor,
13498 action.ignore_brackets,
13499 );
13500 selection.set_head(cursor, SelectionGoal::None);
13501 }
13502 });
13503 });
13504 this.insert("", window, cx);
13505 });
13506 }
13507
13508 pub fn delete_to_next_subword_end(
13509 &mut self,
13510 _: &DeleteToNextSubwordEnd,
13511 window: &mut Window,
13512 cx: &mut Context<Self>,
13513 ) {
13514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13515 self.transact(window, cx, |this, window, cx| {
13516 this.change_selections(Default::default(), window, cx, |s| {
13517 s.move_with(|map, selection| {
13518 if selection.is_empty() {
13519 let mut cursor = movement::next_subword_end(map, selection.head());
13520 cursor =
13521 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13522 selection.set_head(cursor, SelectionGoal::None);
13523 }
13524 });
13525 });
13526 this.insert("", window, cx);
13527 });
13528 }
13529
13530 pub fn move_to_beginning_of_line(
13531 &mut self,
13532 action: &MoveToBeginningOfLine,
13533 window: &mut Window,
13534 cx: &mut Context<Self>,
13535 ) {
13536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13537 self.change_selections(Default::default(), window, cx, |s| {
13538 s.move_cursors_with(|map, head, _| {
13539 (
13540 movement::indented_line_beginning(
13541 map,
13542 head,
13543 action.stop_at_soft_wraps,
13544 action.stop_at_indent,
13545 ),
13546 SelectionGoal::None,
13547 )
13548 });
13549 })
13550 }
13551
13552 pub fn select_to_beginning_of_line(
13553 &mut self,
13554 action: &SelectToBeginningOfLine,
13555 window: &mut Window,
13556 cx: &mut Context<Self>,
13557 ) {
13558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13559 self.change_selections(Default::default(), window, cx, |s| {
13560 s.move_heads_with(|map, head, _| {
13561 (
13562 movement::indented_line_beginning(
13563 map,
13564 head,
13565 action.stop_at_soft_wraps,
13566 action.stop_at_indent,
13567 ),
13568 SelectionGoal::None,
13569 )
13570 });
13571 });
13572 }
13573
13574 pub fn delete_to_beginning_of_line(
13575 &mut self,
13576 action: &DeleteToBeginningOfLine,
13577 window: &mut Window,
13578 cx: &mut Context<Self>,
13579 ) {
13580 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13581 self.transact(window, cx, |this, window, cx| {
13582 this.change_selections(Default::default(), window, cx, |s| {
13583 s.move_with(|_, selection| {
13584 selection.reversed = true;
13585 });
13586 });
13587
13588 this.select_to_beginning_of_line(
13589 &SelectToBeginningOfLine {
13590 stop_at_soft_wraps: false,
13591 stop_at_indent: action.stop_at_indent,
13592 },
13593 window,
13594 cx,
13595 );
13596 this.backspace(&Backspace, window, cx);
13597 });
13598 }
13599
13600 pub fn move_to_end_of_line(
13601 &mut self,
13602 action: &MoveToEndOfLine,
13603 window: &mut Window,
13604 cx: &mut Context<Self>,
13605 ) {
13606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13607 self.change_selections(Default::default(), window, cx, |s| {
13608 s.move_cursors_with(|map, head, _| {
13609 (
13610 movement::line_end(map, head, action.stop_at_soft_wraps),
13611 SelectionGoal::None,
13612 )
13613 });
13614 })
13615 }
13616
13617 pub fn select_to_end_of_line(
13618 &mut self,
13619 action: &SelectToEndOfLine,
13620 window: &mut Window,
13621 cx: &mut Context<Self>,
13622 ) {
13623 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13624 self.change_selections(Default::default(), window, cx, |s| {
13625 s.move_heads_with(|map, head, _| {
13626 (
13627 movement::line_end(map, head, action.stop_at_soft_wraps),
13628 SelectionGoal::None,
13629 )
13630 });
13631 })
13632 }
13633
13634 pub fn delete_to_end_of_line(
13635 &mut self,
13636 _: &DeleteToEndOfLine,
13637 window: &mut Window,
13638 cx: &mut Context<Self>,
13639 ) {
13640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13641 self.transact(window, cx, |this, window, cx| {
13642 this.select_to_end_of_line(
13643 &SelectToEndOfLine {
13644 stop_at_soft_wraps: false,
13645 },
13646 window,
13647 cx,
13648 );
13649 this.delete(&Delete, window, cx);
13650 });
13651 }
13652
13653 pub fn cut_to_end_of_line(
13654 &mut self,
13655 action: &CutToEndOfLine,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13660 self.transact(window, cx, |this, window, cx| {
13661 this.select_to_end_of_line(
13662 &SelectToEndOfLine {
13663 stop_at_soft_wraps: false,
13664 },
13665 window,
13666 cx,
13667 );
13668 if !action.stop_at_newlines {
13669 this.change_selections(Default::default(), window, cx, |s| {
13670 s.move_with(|_, sel| {
13671 if sel.is_empty() {
13672 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13673 }
13674 });
13675 });
13676 }
13677 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13678 let item = this.cut_common(false, window, cx);
13679 cx.write_to_clipboard(item);
13680 });
13681 }
13682
13683 pub fn move_to_start_of_paragraph(
13684 &mut self,
13685 _: &MoveToStartOfParagraph,
13686 window: &mut Window,
13687 cx: &mut Context<Self>,
13688 ) {
13689 if matches!(self.mode, EditorMode::SingleLine) {
13690 cx.propagate();
13691 return;
13692 }
13693 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13694 self.change_selections(Default::default(), window, cx, |s| {
13695 s.move_with(|map, selection| {
13696 selection.collapse_to(
13697 movement::start_of_paragraph(map, selection.head(), 1),
13698 SelectionGoal::None,
13699 )
13700 });
13701 })
13702 }
13703
13704 pub fn move_to_end_of_paragraph(
13705 &mut self,
13706 _: &MoveToEndOfParagraph,
13707 window: &mut Window,
13708 cx: &mut Context<Self>,
13709 ) {
13710 if matches!(self.mode, EditorMode::SingleLine) {
13711 cx.propagate();
13712 return;
13713 }
13714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13715 self.change_selections(Default::default(), window, cx, |s| {
13716 s.move_with(|map, selection| {
13717 selection.collapse_to(
13718 movement::end_of_paragraph(map, selection.head(), 1),
13719 SelectionGoal::None,
13720 )
13721 });
13722 })
13723 }
13724
13725 pub fn select_to_start_of_paragraph(
13726 &mut self,
13727 _: &SelectToStartOfParagraph,
13728 window: &mut Window,
13729 cx: &mut Context<Self>,
13730 ) {
13731 if matches!(self.mode, EditorMode::SingleLine) {
13732 cx.propagate();
13733 return;
13734 }
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13736 self.change_selections(Default::default(), window, cx, |s| {
13737 s.move_heads_with(|map, head, _| {
13738 (
13739 movement::start_of_paragraph(map, head, 1),
13740 SelectionGoal::None,
13741 )
13742 });
13743 })
13744 }
13745
13746 pub fn select_to_end_of_paragraph(
13747 &mut self,
13748 _: &SelectToEndOfParagraph,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) {
13752 if matches!(self.mode, EditorMode::SingleLine) {
13753 cx.propagate();
13754 return;
13755 }
13756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13757 self.change_selections(Default::default(), window, cx, |s| {
13758 s.move_heads_with(|map, head, _| {
13759 (
13760 movement::end_of_paragraph(map, head, 1),
13761 SelectionGoal::None,
13762 )
13763 });
13764 })
13765 }
13766
13767 pub fn move_to_start_of_excerpt(
13768 &mut self,
13769 _: &MoveToStartOfExcerpt,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) {
13773 if matches!(self.mode, EditorMode::SingleLine) {
13774 cx.propagate();
13775 return;
13776 }
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778 self.change_selections(Default::default(), window, cx, |s| {
13779 s.move_with(|map, selection| {
13780 selection.collapse_to(
13781 movement::start_of_excerpt(
13782 map,
13783 selection.head(),
13784 workspace::searchable::Direction::Prev,
13785 ),
13786 SelectionGoal::None,
13787 )
13788 });
13789 })
13790 }
13791
13792 pub fn move_to_start_of_next_excerpt(
13793 &mut self,
13794 _: &MoveToStartOfNextExcerpt,
13795 window: &mut Window,
13796 cx: &mut Context<Self>,
13797 ) {
13798 if matches!(self.mode, EditorMode::SingleLine) {
13799 cx.propagate();
13800 return;
13801 }
13802
13803 self.change_selections(Default::default(), window, cx, |s| {
13804 s.move_with(|map, selection| {
13805 selection.collapse_to(
13806 movement::start_of_excerpt(
13807 map,
13808 selection.head(),
13809 workspace::searchable::Direction::Next,
13810 ),
13811 SelectionGoal::None,
13812 )
13813 });
13814 })
13815 }
13816
13817 pub fn move_to_end_of_excerpt(
13818 &mut self,
13819 _: &MoveToEndOfExcerpt,
13820 window: &mut Window,
13821 cx: &mut Context<Self>,
13822 ) {
13823 if matches!(self.mode, EditorMode::SingleLine) {
13824 cx.propagate();
13825 return;
13826 }
13827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13828 self.change_selections(Default::default(), window, cx, |s| {
13829 s.move_with(|map, selection| {
13830 selection.collapse_to(
13831 movement::end_of_excerpt(
13832 map,
13833 selection.head(),
13834 workspace::searchable::Direction::Next,
13835 ),
13836 SelectionGoal::None,
13837 )
13838 });
13839 })
13840 }
13841
13842 pub fn move_to_end_of_previous_excerpt(
13843 &mut self,
13844 _: &MoveToEndOfPreviousExcerpt,
13845 window: &mut Window,
13846 cx: &mut Context<Self>,
13847 ) {
13848 if matches!(self.mode, EditorMode::SingleLine) {
13849 cx.propagate();
13850 return;
13851 }
13852 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13853 self.change_selections(Default::default(), window, cx, |s| {
13854 s.move_with(|map, selection| {
13855 selection.collapse_to(
13856 movement::end_of_excerpt(
13857 map,
13858 selection.head(),
13859 workspace::searchable::Direction::Prev,
13860 ),
13861 SelectionGoal::None,
13862 )
13863 });
13864 })
13865 }
13866
13867 pub fn select_to_start_of_excerpt(
13868 &mut self,
13869 _: &SelectToStartOfExcerpt,
13870 window: &mut Window,
13871 cx: &mut Context<Self>,
13872 ) {
13873 if matches!(self.mode, EditorMode::SingleLine) {
13874 cx.propagate();
13875 return;
13876 }
13877 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13878 self.change_selections(Default::default(), window, cx, |s| {
13879 s.move_heads_with(|map, head, _| {
13880 (
13881 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13882 SelectionGoal::None,
13883 )
13884 });
13885 })
13886 }
13887
13888 pub fn select_to_start_of_next_excerpt(
13889 &mut self,
13890 _: &SelectToStartOfNextExcerpt,
13891 window: &mut Window,
13892 cx: &mut Context<Self>,
13893 ) {
13894 if matches!(self.mode, EditorMode::SingleLine) {
13895 cx.propagate();
13896 return;
13897 }
13898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13899 self.change_selections(Default::default(), window, cx, |s| {
13900 s.move_heads_with(|map, head, _| {
13901 (
13902 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13903 SelectionGoal::None,
13904 )
13905 });
13906 })
13907 }
13908
13909 pub fn select_to_end_of_excerpt(
13910 &mut self,
13911 _: &SelectToEndOfExcerpt,
13912 window: &mut Window,
13913 cx: &mut Context<Self>,
13914 ) {
13915 if matches!(self.mode, EditorMode::SingleLine) {
13916 cx.propagate();
13917 return;
13918 }
13919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13920 self.change_selections(Default::default(), window, cx, |s| {
13921 s.move_heads_with(|map, head, _| {
13922 (
13923 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13924 SelectionGoal::None,
13925 )
13926 });
13927 })
13928 }
13929
13930 pub fn select_to_end_of_previous_excerpt(
13931 &mut self,
13932 _: &SelectToEndOfPreviousExcerpt,
13933 window: &mut Window,
13934 cx: &mut Context<Self>,
13935 ) {
13936 if matches!(self.mode, EditorMode::SingleLine) {
13937 cx.propagate();
13938 return;
13939 }
13940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13941 self.change_selections(Default::default(), window, cx, |s| {
13942 s.move_heads_with(|map, head, _| {
13943 (
13944 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13945 SelectionGoal::None,
13946 )
13947 });
13948 })
13949 }
13950
13951 pub fn move_to_beginning(
13952 &mut self,
13953 _: &MoveToBeginning,
13954 window: &mut Window,
13955 cx: &mut Context<Self>,
13956 ) {
13957 if matches!(self.mode, EditorMode::SingleLine) {
13958 cx.propagate();
13959 return;
13960 }
13961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13962 self.change_selections(Default::default(), window, cx, |s| {
13963 s.select_ranges(vec![0..0]);
13964 });
13965 }
13966
13967 pub fn select_to_beginning(
13968 &mut self,
13969 _: &SelectToBeginning,
13970 window: &mut Window,
13971 cx: &mut Context<Self>,
13972 ) {
13973 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13974 selection.set_head(Point::zero(), SelectionGoal::None);
13975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13976 self.change_selections(Default::default(), window, cx, |s| {
13977 s.select(vec![selection]);
13978 });
13979 }
13980
13981 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13982 if matches!(self.mode, EditorMode::SingleLine) {
13983 cx.propagate();
13984 return;
13985 }
13986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13987 let cursor = self.buffer.read(cx).read(cx).len();
13988 self.change_selections(Default::default(), window, cx, |s| {
13989 s.select_ranges(vec![cursor..cursor])
13990 });
13991 }
13992
13993 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13994 self.nav_history = nav_history;
13995 }
13996
13997 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13998 self.nav_history.as_ref()
13999 }
14000
14001 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14002 self.push_to_nav_history(
14003 self.selections.newest_anchor().head(),
14004 None,
14005 false,
14006 true,
14007 cx,
14008 );
14009 }
14010
14011 fn push_to_nav_history(
14012 &mut self,
14013 cursor_anchor: Anchor,
14014 new_position: Option<Point>,
14015 is_deactivate: bool,
14016 always: bool,
14017 cx: &mut Context<Self>,
14018 ) {
14019 if let Some(nav_history) = self.nav_history.as_mut() {
14020 let buffer = self.buffer.read(cx).read(cx);
14021 let cursor_position = cursor_anchor.to_point(&buffer);
14022 let scroll_state = self.scroll_manager.anchor();
14023 let scroll_top_row = scroll_state.top_row(&buffer);
14024 drop(buffer);
14025
14026 if let Some(new_position) = new_position {
14027 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14028 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14029 return;
14030 }
14031 }
14032
14033 nav_history.push(
14034 Some(NavigationData {
14035 cursor_anchor,
14036 cursor_position,
14037 scroll_anchor: scroll_state,
14038 scroll_top_row,
14039 }),
14040 cx,
14041 );
14042 cx.emit(EditorEvent::PushedToNavHistory {
14043 anchor: cursor_anchor,
14044 is_deactivate,
14045 })
14046 }
14047 }
14048
14049 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14050 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14051 let buffer = self.buffer.read(cx).snapshot(cx);
14052 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14053 selection.set_head(buffer.len(), SelectionGoal::None);
14054 self.change_selections(Default::default(), window, cx, |s| {
14055 s.select(vec![selection]);
14056 });
14057 }
14058
14059 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14061 let end = self.buffer.read(cx).read(cx).len();
14062 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14063 s.select_ranges(vec![0..end]);
14064 });
14065 }
14066
14067 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14070 let mut selections = self.selections.all::<Point>(&display_map);
14071 let max_point = display_map.buffer_snapshot().max_point();
14072 for selection in &mut selections {
14073 let rows = selection.spanned_rows(true, &display_map);
14074 selection.start = Point::new(rows.start.0, 0);
14075 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14076 selection.reversed = false;
14077 }
14078 self.change_selections(Default::default(), window, cx, |s| {
14079 s.select(selections);
14080 });
14081 }
14082
14083 pub fn split_selection_into_lines(
14084 &mut self,
14085 action: &SplitSelectionIntoLines,
14086 window: &mut Window,
14087 cx: &mut Context<Self>,
14088 ) {
14089 let selections = self
14090 .selections
14091 .all::<Point>(&self.display_snapshot(cx))
14092 .into_iter()
14093 .map(|selection| selection.start..selection.end)
14094 .collect::<Vec<_>>();
14095 self.unfold_ranges(&selections, true, true, cx);
14096
14097 let mut new_selection_ranges = Vec::new();
14098 {
14099 let buffer = self.buffer.read(cx).read(cx);
14100 for selection in selections {
14101 for row in selection.start.row..selection.end.row {
14102 let line_start = Point::new(row, 0);
14103 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14104
14105 if action.keep_selections {
14106 // Keep the selection range for each line
14107 let selection_start = if row == selection.start.row {
14108 selection.start
14109 } else {
14110 line_start
14111 };
14112 new_selection_ranges.push(selection_start..line_end);
14113 } else {
14114 // Collapse to cursor at end of line
14115 new_selection_ranges.push(line_end..line_end);
14116 }
14117 }
14118
14119 let is_multiline_selection = selection.start.row != selection.end.row;
14120 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14121 // so this action feels more ergonomic when paired with other selection operations
14122 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14123 if !should_skip_last {
14124 if action.keep_selections {
14125 if is_multiline_selection {
14126 let line_start = Point::new(selection.end.row, 0);
14127 new_selection_ranges.push(line_start..selection.end);
14128 } else {
14129 new_selection_ranges.push(selection.start..selection.end);
14130 }
14131 } else {
14132 new_selection_ranges.push(selection.end..selection.end);
14133 }
14134 }
14135 }
14136 }
14137 self.change_selections(Default::default(), window, cx, |s| {
14138 s.select_ranges(new_selection_ranges);
14139 });
14140 }
14141
14142 pub fn add_selection_above(
14143 &mut self,
14144 action: &AddSelectionAbove,
14145 window: &mut Window,
14146 cx: &mut Context<Self>,
14147 ) {
14148 self.add_selection(true, action.skip_soft_wrap, window, cx);
14149 }
14150
14151 pub fn add_selection_below(
14152 &mut self,
14153 action: &AddSelectionBelow,
14154 window: &mut Window,
14155 cx: &mut Context<Self>,
14156 ) {
14157 self.add_selection(false, action.skip_soft_wrap, window, cx);
14158 }
14159
14160 fn add_selection(
14161 &mut self,
14162 above: bool,
14163 skip_soft_wrap: bool,
14164 window: &mut Window,
14165 cx: &mut Context<Self>,
14166 ) {
14167 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14168
14169 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14170 let all_selections = self.selections.all::<Point>(&display_map);
14171 let text_layout_details = self.text_layout_details(window);
14172
14173 let (mut columnar_selections, new_selections_to_columnarize) = {
14174 if let Some(state) = self.add_selections_state.as_ref() {
14175 let columnar_selection_ids: HashSet<_> = state
14176 .groups
14177 .iter()
14178 .flat_map(|group| group.stack.iter())
14179 .copied()
14180 .collect();
14181
14182 all_selections
14183 .into_iter()
14184 .partition(|s| columnar_selection_ids.contains(&s.id))
14185 } else {
14186 (Vec::new(), all_selections)
14187 }
14188 };
14189
14190 let mut state = self
14191 .add_selections_state
14192 .take()
14193 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14194
14195 for selection in new_selections_to_columnarize {
14196 let range = selection.display_range(&display_map).sorted();
14197 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14198 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14199 let positions = start_x.min(end_x)..start_x.max(end_x);
14200 let mut stack = Vec::new();
14201 for row in range.start.row().0..=range.end.row().0 {
14202 if let Some(selection) = self.selections.build_columnar_selection(
14203 &display_map,
14204 DisplayRow(row),
14205 &positions,
14206 selection.reversed,
14207 &text_layout_details,
14208 ) {
14209 stack.push(selection.id);
14210 columnar_selections.push(selection);
14211 }
14212 }
14213 if !stack.is_empty() {
14214 if above {
14215 stack.reverse();
14216 }
14217 state.groups.push(AddSelectionsGroup { above, stack });
14218 }
14219 }
14220
14221 let mut final_selections = Vec::new();
14222 let end_row = if above {
14223 DisplayRow(0)
14224 } else {
14225 display_map.max_point().row()
14226 };
14227
14228 let mut last_added_item_per_group = HashMap::default();
14229 for group in state.groups.iter_mut() {
14230 if let Some(last_id) = group.stack.last() {
14231 last_added_item_per_group.insert(*last_id, group);
14232 }
14233 }
14234
14235 for selection in columnar_selections {
14236 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14237 if above == group.above {
14238 let range = selection.display_range(&display_map).sorted();
14239 debug_assert_eq!(range.start.row(), range.end.row());
14240 let mut row = range.start.row();
14241 let positions =
14242 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14243 Pixels::from(start)..Pixels::from(end)
14244 } else {
14245 let start_x =
14246 display_map.x_for_display_point(range.start, &text_layout_details);
14247 let end_x =
14248 display_map.x_for_display_point(range.end, &text_layout_details);
14249 start_x.min(end_x)..start_x.max(end_x)
14250 };
14251
14252 let mut maybe_new_selection = None;
14253 let direction = if above { -1 } else { 1 };
14254
14255 while row != end_row {
14256 if skip_soft_wrap {
14257 row = display_map
14258 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14259 .row();
14260 } else if above {
14261 row.0 -= 1;
14262 } else {
14263 row.0 += 1;
14264 }
14265
14266 if let Some(new_selection) = self.selections.build_columnar_selection(
14267 &display_map,
14268 row,
14269 &positions,
14270 selection.reversed,
14271 &text_layout_details,
14272 ) {
14273 maybe_new_selection = Some(new_selection);
14274 break;
14275 }
14276 }
14277
14278 if let Some(new_selection) = maybe_new_selection {
14279 group.stack.push(new_selection.id);
14280 if above {
14281 final_selections.push(new_selection);
14282 final_selections.push(selection);
14283 } else {
14284 final_selections.push(selection);
14285 final_selections.push(new_selection);
14286 }
14287 } else {
14288 final_selections.push(selection);
14289 }
14290 } else {
14291 group.stack.pop();
14292 }
14293 } else {
14294 final_selections.push(selection);
14295 }
14296 }
14297
14298 self.change_selections(Default::default(), window, cx, |s| {
14299 s.select(final_selections);
14300 });
14301
14302 let final_selection_ids: HashSet<_> = self
14303 .selections
14304 .all::<Point>(&display_map)
14305 .iter()
14306 .map(|s| s.id)
14307 .collect();
14308 state.groups.retain_mut(|group| {
14309 // selections might get merged above so we remove invalid items from stacks
14310 group.stack.retain(|id| final_selection_ids.contains(id));
14311
14312 // single selection in stack can be treated as initial state
14313 group.stack.len() > 1
14314 });
14315
14316 if !state.groups.is_empty() {
14317 self.add_selections_state = Some(state);
14318 }
14319 }
14320
14321 fn select_match_ranges(
14322 &mut self,
14323 range: Range<usize>,
14324 reversed: bool,
14325 replace_newest: bool,
14326 auto_scroll: Option<Autoscroll>,
14327 window: &mut Window,
14328 cx: &mut Context<Editor>,
14329 ) {
14330 self.unfold_ranges(
14331 std::slice::from_ref(&range),
14332 false,
14333 auto_scroll.is_some(),
14334 cx,
14335 );
14336 let effects = if let Some(scroll) = auto_scroll {
14337 SelectionEffects::scroll(scroll)
14338 } else {
14339 SelectionEffects::no_scroll()
14340 };
14341 self.change_selections(effects, window, cx, |s| {
14342 if replace_newest {
14343 s.delete(s.newest_anchor().id);
14344 }
14345 if reversed {
14346 s.insert_range(range.end..range.start);
14347 } else {
14348 s.insert_range(range);
14349 }
14350 });
14351 }
14352
14353 pub fn select_next_match_internal(
14354 &mut self,
14355 display_map: &DisplaySnapshot,
14356 replace_newest: bool,
14357 autoscroll: Option<Autoscroll>,
14358 window: &mut Window,
14359 cx: &mut Context<Self>,
14360 ) -> Result<()> {
14361 let buffer = display_map.buffer_snapshot();
14362 let mut selections = self.selections.all::<usize>(&display_map);
14363 if let Some(mut select_next_state) = self.select_next_state.take() {
14364 let query = &select_next_state.query;
14365 if !select_next_state.done {
14366 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14367 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14368 let mut next_selected_range = None;
14369
14370 let bytes_after_last_selection =
14371 buffer.bytes_in_range(last_selection.end..buffer.len());
14372 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14373 let query_matches = query
14374 .stream_find_iter(bytes_after_last_selection)
14375 .map(|result| (last_selection.end, result))
14376 .chain(
14377 query
14378 .stream_find_iter(bytes_before_first_selection)
14379 .map(|result| (0, result)),
14380 );
14381
14382 for (start_offset, query_match) in query_matches {
14383 let query_match = query_match.unwrap(); // can only fail due to I/O
14384 let offset_range =
14385 start_offset + query_match.start()..start_offset + query_match.end();
14386
14387 if !select_next_state.wordwise
14388 || (!buffer.is_inside_word(offset_range.start, None)
14389 && !buffer.is_inside_word(offset_range.end, None))
14390 {
14391 let idx = selections
14392 .partition_point(|selection| selection.end <= offset_range.start);
14393 let overlaps = selections
14394 .get(idx)
14395 .map_or(false, |selection| selection.start < offset_range.end);
14396
14397 if !overlaps {
14398 next_selected_range = Some(offset_range);
14399 break;
14400 }
14401 }
14402 }
14403
14404 if let Some(next_selected_range) = next_selected_range {
14405 self.select_match_ranges(
14406 next_selected_range,
14407 last_selection.reversed,
14408 replace_newest,
14409 autoscroll,
14410 window,
14411 cx,
14412 );
14413 } else {
14414 select_next_state.done = true;
14415 }
14416 }
14417
14418 self.select_next_state = Some(select_next_state);
14419 } else {
14420 let mut only_carets = true;
14421 let mut same_text_selected = true;
14422 let mut selected_text = None;
14423
14424 let mut selections_iter = selections.iter().peekable();
14425 while let Some(selection) = selections_iter.next() {
14426 if selection.start != selection.end {
14427 only_carets = false;
14428 }
14429
14430 if same_text_selected {
14431 if selected_text.is_none() {
14432 selected_text =
14433 Some(buffer.text_for_range(selection.range()).collect::<String>());
14434 }
14435
14436 if let Some(next_selection) = selections_iter.peek() {
14437 if next_selection.range().len() == selection.range().len() {
14438 let next_selected_text = buffer
14439 .text_for_range(next_selection.range())
14440 .collect::<String>();
14441 if Some(next_selected_text) != selected_text {
14442 same_text_selected = false;
14443 selected_text = None;
14444 }
14445 } else {
14446 same_text_selected = false;
14447 selected_text = None;
14448 }
14449 }
14450 }
14451 }
14452
14453 if only_carets {
14454 for selection in &mut selections {
14455 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14456 selection.start = word_range.start;
14457 selection.end = word_range.end;
14458 selection.goal = SelectionGoal::None;
14459 selection.reversed = false;
14460 self.select_match_ranges(
14461 selection.start..selection.end,
14462 selection.reversed,
14463 replace_newest,
14464 autoscroll,
14465 window,
14466 cx,
14467 );
14468 }
14469
14470 if selections.len() == 1 {
14471 let selection = selections
14472 .last()
14473 .expect("ensured that there's only one selection");
14474 let query = buffer
14475 .text_for_range(selection.start..selection.end)
14476 .collect::<String>();
14477 let is_empty = query.is_empty();
14478 let select_state = SelectNextState {
14479 query: AhoCorasick::new(&[query])?,
14480 wordwise: true,
14481 done: is_empty,
14482 };
14483 self.select_next_state = Some(select_state);
14484 } else {
14485 self.select_next_state = None;
14486 }
14487 } else if let Some(selected_text) = selected_text {
14488 self.select_next_state = Some(SelectNextState {
14489 query: AhoCorasick::new(&[selected_text])?,
14490 wordwise: false,
14491 done: false,
14492 });
14493 self.select_next_match_internal(
14494 display_map,
14495 replace_newest,
14496 autoscroll,
14497 window,
14498 cx,
14499 )?;
14500 }
14501 }
14502 Ok(())
14503 }
14504
14505 pub fn select_all_matches(
14506 &mut self,
14507 _action: &SelectAllMatches,
14508 window: &mut Window,
14509 cx: &mut Context<Self>,
14510 ) -> Result<()> {
14511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14512
14513 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14514
14515 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14516 let Some(select_next_state) = self.select_next_state.as_mut() else {
14517 return Ok(());
14518 };
14519 if select_next_state.done {
14520 return Ok(());
14521 }
14522
14523 let mut new_selections = Vec::new();
14524
14525 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14526 let buffer = display_map.buffer_snapshot();
14527 let query_matches = select_next_state
14528 .query
14529 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14530
14531 for query_match in query_matches.into_iter() {
14532 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14533 let offset_range = if reversed {
14534 query_match.end()..query_match.start()
14535 } else {
14536 query_match.start()..query_match.end()
14537 };
14538
14539 if !select_next_state.wordwise
14540 || (!buffer.is_inside_word(offset_range.start, None)
14541 && !buffer.is_inside_word(offset_range.end, None))
14542 {
14543 new_selections.push(offset_range.start..offset_range.end);
14544 }
14545 }
14546
14547 select_next_state.done = true;
14548
14549 if new_selections.is_empty() {
14550 log::error!("bug: new_selections is empty in select_all_matches");
14551 return Ok(());
14552 }
14553
14554 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14555 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14556 selections.select_ranges(new_selections)
14557 });
14558
14559 Ok(())
14560 }
14561
14562 pub fn select_next(
14563 &mut self,
14564 action: &SelectNext,
14565 window: &mut Window,
14566 cx: &mut Context<Self>,
14567 ) -> Result<()> {
14568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14569 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14570 self.select_next_match_internal(
14571 &display_map,
14572 action.replace_newest,
14573 Some(Autoscroll::newest()),
14574 window,
14575 cx,
14576 )?;
14577 Ok(())
14578 }
14579
14580 pub fn select_previous(
14581 &mut self,
14582 action: &SelectPrevious,
14583 window: &mut Window,
14584 cx: &mut Context<Self>,
14585 ) -> Result<()> {
14586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14588 let buffer = display_map.buffer_snapshot();
14589 let mut selections = self.selections.all::<usize>(&display_map);
14590 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14591 let query = &select_prev_state.query;
14592 if !select_prev_state.done {
14593 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14594 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14595 let mut next_selected_range = None;
14596 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14597 let bytes_before_last_selection =
14598 buffer.reversed_bytes_in_range(0..last_selection.start);
14599 let bytes_after_first_selection =
14600 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14601 let query_matches = query
14602 .stream_find_iter(bytes_before_last_selection)
14603 .map(|result| (last_selection.start, result))
14604 .chain(
14605 query
14606 .stream_find_iter(bytes_after_first_selection)
14607 .map(|result| (buffer.len(), result)),
14608 );
14609 for (end_offset, query_match) in query_matches {
14610 let query_match = query_match.unwrap(); // can only fail due to I/O
14611 let offset_range =
14612 end_offset - query_match.end()..end_offset - query_match.start();
14613
14614 if !select_prev_state.wordwise
14615 || (!buffer.is_inside_word(offset_range.start, None)
14616 && !buffer.is_inside_word(offset_range.end, None))
14617 {
14618 next_selected_range = Some(offset_range);
14619 break;
14620 }
14621 }
14622
14623 if let Some(next_selected_range) = next_selected_range {
14624 self.select_match_ranges(
14625 next_selected_range,
14626 last_selection.reversed,
14627 action.replace_newest,
14628 Some(Autoscroll::newest()),
14629 window,
14630 cx,
14631 );
14632 } else {
14633 select_prev_state.done = true;
14634 }
14635 }
14636
14637 self.select_prev_state = Some(select_prev_state);
14638 } else {
14639 let mut only_carets = true;
14640 let mut same_text_selected = true;
14641 let mut selected_text = None;
14642
14643 let mut selections_iter = selections.iter().peekable();
14644 while let Some(selection) = selections_iter.next() {
14645 if selection.start != selection.end {
14646 only_carets = false;
14647 }
14648
14649 if same_text_selected {
14650 if selected_text.is_none() {
14651 selected_text =
14652 Some(buffer.text_for_range(selection.range()).collect::<String>());
14653 }
14654
14655 if let Some(next_selection) = selections_iter.peek() {
14656 if next_selection.range().len() == selection.range().len() {
14657 let next_selected_text = buffer
14658 .text_for_range(next_selection.range())
14659 .collect::<String>();
14660 if Some(next_selected_text) != selected_text {
14661 same_text_selected = false;
14662 selected_text = None;
14663 }
14664 } else {
14665 same_text_selected = false;
14666 selected_text = None;
14667 }
14668 }
14669 }
14670 }
14671
14672 if only_carets {
14673 for selection in &mut selections {
14674 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14675 selection.start = word_range.start;
14676 selection.end = word_range.end;
14677 selection.goal = SelectionGoal::None;
14678 selection.reversed = false;
14679 self.select_match_ranges(
14680 selection.start..selection.end,
14681 selection.reversed,
14682 action.replace_newest,
14683 Some(Autoscroll::newest()),
14684 window,
14685 cx,
14686 );
14687 }
14688 if selections.len() == 1 {
14689 let selection = selections
14690 .last()
14691 .expect("ensured that there's only one selection");
14692 let query = buffer
14693 .text_for_range(selection.start..selection.end)
14694 .collect::<String>();
14695 let is_empty = query.is_empty();
14696 let select_state = SelectNextState {
14697 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14698 wordwise: true,
14699 done: is_empty,
14700 };
14701 self.select_prev_state = Some(select_state);
14702 } else {
14703 self.select_prev_state = None;
14704 }
14705 } else if let Some(selected_text) = selected_text {
14706 self.select_prev_state = Some(SelectNextState {
14707 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14708 wordwise: false,
14709 done: false,
14710 });
14711 self.select_previous(action, window, cx)?;
14712 }
14713 }
14714 Ok(())
14715 }
14716
14717 pub fn find_next_match(
14718 &mut self,
14719 _: &FindNextMatch,
14720 window: &mut Window,
14721 cx: &mut Context<Self>,
14722 ) -> Result<()> {
14723 let selections = self.selections.disjoint_anchors_arc();
14724 match selections.first() {
14725 Some(first) if selections.len() >= 2 => {
14726 self.change_selections(Default::default(), window, cx, |s| {
14727 s.select_ranges([first.range()]);
14728 });
14729 }
14730 _ => self.select_next(
14731 &SelectNext {
14732 replace_newest: true,
14733 },
14734 window,
14735 cx,
14736 )?,
14737 }
14738 Ok(())
14739 }
14740
14741 pub fn find_previous_match(
14742 &mut self,
14743 _: &FindPreviousMatch,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) -> Result<()> {
14747 let selections = self.selections.disjoint_anchors_arc();
14748 match selections.last() {
14749 Some(last) if selections.len() >= 2 => {
14750 self.change_selections(Default::default(), window, cx, |s| {
14751 s.select_ranges([last.range()]);
14752 });
14753 }
14754 _ => self.select_previous(
14755 &SelectPrevious {
14756 replace_newest: true,
14757 },
14758 window,
14759 cx,
14760 )?,
14761 }
14762 Ok(())
14763 }
14764
14765 pub fn toggle_comments(
14766 &mut self,
14767 action: &ToggleComments,
14768 window: &mut Window,
14769 cx: &mut Context<Self>,
14770 ) {
14771 if self.read_only(cx) {
14772 return;
14773 }
14774 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14775 let text_layout_details = &self.text_layout_details(window);
14776 self.transact(window, cx, |this, window, cx| {
14777 let mut selections = this
14778 .selections
14779 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14780 let mut edits = Vec::new();
14781 let mut selection_edit_ranges = Vec::new();
14782 let mut last_toggled_row = None;
14783 let snapshot = this.buffer.read(cx).read(cx);
14784 let empty_str: Arc<str> = Arc::default();
14785 let mut suffixes_inserted = Vec::new();
14786 let ignore_indent = action.ignore_indent;
14787
14788 fn comment_prefix_range(
14789 snapshot: &MultiBufferSnapshot,
14790 row: MultiBufferRow,
14791 comment_prefix: &str,
14792 comment_prefix_whitespace: &str,
14793 ignore_indent: bool,
14794 ) -> Range<Point> {
14795 let indent_size = if ignore_indent {
14796 0
14797 } else {
14798 snapshot.indent_size_for_line(row).len
14799 };
14800
14801 let start = Point::new(row.0, indent_size);
14802
14803 let mut line_bytes = snapshot
14804 .bytes_in_range(start..snapshot.max_point())
14805 .flatten()
14806 .copied();
14807
14808 // If this line currently begins with the line comment prefix, then record
14809 // the range containing the prefix.
14810 if line_bytes
14811 .by_ref()
14812 .take(comment_prefix.len())
14813 .eq(comment_prefix.bytes())
14814 {
14815 // Include any whitespace that matches the comment prefix.
14816 let matching_whitespace_len = line_bytes
14817 .zip(comment_prefix_whitespace.bytes())
14818 .take_while(|(a, b)| a == b)
14819 .count() as u32;
14820 let end = Point::new(
14821 start.row,
14822 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14823 );
14824 start..end
14825 } else {
14826 start..start
14827 }
14828 }
14829
14830 fn comment_suffix_range(
14831 snapshot: &MultiBufferSnapshot,
14832 row: MultiBufferRow,
14833 comment_suffix: &str,
14834 comment_suffix_has_leading_space: bool,
14835 ) -> Range<Point> {
14836 let end = Point::new(row.0, snapshot.line_len(row));
14837 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14838
14839 let mut line_end_bytes = snapshot
14840 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14841 .flatten()
14842 .copied();
14843
14844 let leading_space_len = if suffix_start_column > 0
14845 && line_end_bytes.next() == Some(b' ')
14846 && comment_suffix_has_leading_space
14847 {
14848 1
14849 } else {
14850 0
14851 };
14852
14853 // If this line currently begins with the line comment prefix, then record
14854 // the range containing the prefix.
14855 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14856 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14857 start..end
14858 } else {
14859 end..end
14860 }
14861 }
14862
14863 // TODO: Handle selections that cross excerpts
14864 for selection in &mut selections {
14865 let start_column = snapshot
14866 .indent_size_for_line(MultiBufferRow(selection.start.row))
14867 .len;
14868 let language = if let Some(language) =
14869 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14870 {
14871 language
14872 } else {
14873 continue;
14874 };
14875
14876 selection_edit_ranges.clear();
14877
14878 // If multiple selections contain a given row, avoid processing that
14879 // row more than once.
14880 let mut start_row = MultiBufferRow(selection.start.row);
14881 if last_toggled_row == Some(start_row) {
14882 start_row = start_row.next_row();
14883 }
14884 let end_row =
14885 if selection.end.row > selection.start.row && selection.end.column == 0 {
14886 MultiBufferRow(selection.end.row - 1)
14887 } else {
14888 MultiBufferRow(selection.end.row)
14889 };
14890 last_toggled_row = Some(end_row);
14891
14892 if start_row > end_row {
14893 continue;
14894 }
14895
14896 // If the language has line comments, toggle those.
14897 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14898
14899 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14900 if ignore_indent {
14901 full_comment_prefixes = full_comment_prefixes
14902 .into_iter()
14903 .map(|s| Arc::from(s.trim_end()))
14904 .collect();
14905 }
14906
14907 if !full_comment_prefixes.is_empty() {
14908 let first_prefix = full_comment_prefixes
14909 .first()
14910 .expect("prefixes is non-empty");
14911 let prefix_trimmed_lengths = full_comment_prefixes
14912 .iter()
14913 .map(|p| p.trim_end_matches(' ').len())
14914 .collect::<SmallVec<[usize; 4]>>();
14915
14916 let mut all_selection_lines_are_comments = true;
14917
14918 for row in start_row.0..=end_row.0 {
14919 let row = MultiBufferRow(row);
14920 if start_row < end_row && snapshot.is_line_blank(row) {
14921 continue;
14922 }
14923
14924 let prefix_range = full_comment_prefixes
14925 .iter()
14926 .zip(prefix_trimmed_lengths.iter().copied())
14927 .map(|(prefix, trimmed_prefix_len)| {
14928 comment_prefix_range(
14929 snapshot.deref(),
14930 row,
14931 &prefix[..trimmed_prefix_len],
14932 &prefix[trimmed_prefix_len..],
14933 ignore_indent,
14934 )
14935 })
14936 .max_by_key(|range| range.end.column - range.start.column)
14937 .expect("prefixes is non-empty");
14938
14939 if prefix_range.is_empty() {
14940 all_selection_lines_are_comments = false;
14941 }
14942
14943 selection_edit_ranges.push(prefix_range);
14944 }
14945
14946 if all_selection_lines_are_comments {
14947 edits.extend(
14948 selection_edit_ranges
14949 .iter()
14950 .cloned()
14951 .map(|range| (range, empty_str.clone())),
14952 );
14953 } else {
14954 let min_column = selection_edit_ranges
14955 .iter()
14956 .map(|range| range.start.column)
14957 .min()
14958 .unwrap_or(0);
14959 edits.extend(selection_edit_ranges.iter().map(|range| {
14960 let position = Point::new(range.start.row, min_column);
14961 (position..position, first_prefix.clone())
14962 }));
14963 }
14964 } else if let Some(BlockCommentConfig {
14965 start: full_comment_prefix,
14966 end: comment_suffix,
14967 ..
14968 }) = language.block_comment()
14969 {
14970 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14971 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14972 let prefix_range = comment_prefix_range(
14973 snapshot.deref(),
14974 start_row,
14975 comment_prefix,
14976 comment_prefix_whitespace,
14977 ignore_indent,
14978 );
14979 let suffix_range = comment_suffix_range(
14980 snapshot.deref(),
14981 end_row,
14982 comment_suffix.trim_start_matches(' '),
14983 comment_suffix.starts_with(' '),
14984 );
14985
14986 if prefix_range.is_empty() || suffix_range.is_empty() {
14987 edits.push((
14988 prefix_range.start..prefix_range.start,
14989 full_comment_prefix.clone(),
14990 ));
14991 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14992 suffixes_inserted.push((end_row, comment_suffix.len()));
14993 } else {
14994 edits.push((prefix_range, empty_str.clone()));
14995 edits.push((suffix_range, empty_str.clone()));
14996 }
14997 } else {
14998 continue;
14999 }
15000 }
15001
15002 drop(snapshot);
15003 this.buffer.update(cx, |buffer, cx| {
15004 buffer.edit(edits, None, cx);
15005 });
15006
15007 // Adjust selections so that they end before any comment suffixes that
15008 // were inserted.
15009 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15010 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15011 let snapshot = this.buffer.read(cx).read(cx);
15012 for selection in &mut selections {
15013 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15014 match row.cmp(&MultiBufferRow(selection.end.row)) {
15015 Ordering::Less => {
15016 suffixes_inserted.next();
15017 continue;
15018 }
15019 Ordering::Greater => break,
15020 Ordering::Equal => {
15021 if selection.end.column == snapshot.line_len(row) {
15022 if selection.is_empty() {
15023 selection.start.column -= suffix_len as u32;
15024 }
15025 selection.end.column -= suffix_len as u32;
15026 }
15027 break;
15028 }
15029 }
15030 }
15031 }
15032
15033 drop(snapshot);
15034 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15035
15036 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15037 let selections_on_single_row = selections.windows(2).all(|selections| {
15038 selections[0].start.row == selections[1].start.row
15039 && selections[0].end.row == selections[1].end.row
15040 && selections[0].start.row == selections[0].end.row
15041 });
15042 let selections_selecting = selections
15043 .iter()
15044 .any(|selection| selection.start != selection.end);
15045 let advance_downwards = action.advance_downwards
15046 && selections_on_single_row
15047 && !selections_selecting
15048 && !matches!(this.mode, EditorMode::SingleLine);
15049
15050 if advance_downwards {
15051 let snapshot = this.buffer.read(cx).snapshot(cx);
15052
15053 this.change_selections(Default::default(), window, cx, |s| {
15054 s.move_cursors_with(|display_snapshot, display_point, _| {
15055 let mut point = display_point.to_point(display_snapshot);
15056 point.row += 1;
15057 point = snapshot.clip_point(point, Bias::Left);
15058 let display_point = point.to_display_point(display_snapshot);
15059 let goal = SelectionGoal::HorizontalPosition(
15060 display_snapshot
15061 .x_for_display_point(display_point, text_layout_details)
15062 .into(),
15063 );
15064 (display_point, goal)
15065 })
15066 });
15067 }
15068 });
15069 }
15070
15071 pub fn select_enclosing_symbol(
15072 &mut self,
15073 _: &SelectEnclosingSymbol,
15074 window: &mut Window,
15075 cx: &mut Context<Self>,
15076 ) {
15077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15078
15079 let buffer = self.buffer.read(cx).snapshot(cx);
15080 let old_selections = self
15081 .selections
15082 .all::<usize>(&self.display_snapshot(cx))
15083 .into_boxed_slice();
15084
15085 fn update_selection(
15086 selection: &Selection<usize>,
15087 buffer_snap: &MultiBufferSnapshot,
15088 ) -> Option<Selection<usize>> {
15089 let cursor = selection.head();
15090 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15091 for symbol in symbols.iter().rev() {
15092 let start = symbol.range.start.to_offset(buffer_snap);
15093 let end = symbol.range.end.to_offset(buffer_snap);
15094 let new_range = start..end;
15095 if start < selection.start || end > selection.end {
15096 return Some(Selection {
15097 id: selection.id,
15098 start: new_range.start,
15099 end: new_range.end,
15100 goal: SelectionGoal::None,
15101 reversed: selection.reversed,
15102 });
15103 }
15104 }
15105 None
15106 }
15107
15108 let mut selected_larger_symbol = false;
15109 let new_selections = old_selections
15110 .iter()
15111 .map(|selection| match update_selection(selection, &buffer) {
15112 Some(new_selection) => {
15113 if new_selection.range() != selection.range() {
15114 selected_larger_symbol = true;
15115 }
15116 new_selection
15117 }
15118 None => selection.clone(),
15119 })
15120 .collect::<Vec<_>>();
15121
15122 if selected_larger_symbol {
15123 self.change_selections(Default::default(), window, cx, |s| {
15124 s.select(new_selections);
15125 });
15126 }
15127 }
15128
15129 pub fn select_larger_syntax_node(
15130 &mut self,
15131 _: &SelectLargerSyntaxNode,
15132 window: &mut Window,
15133 cx: &mut Context<Self>,
15134 ) {
15135 let Some(visible_row_count) = self.visible_row_count() else {
15136 return;
15137 };
15138 let old_selections: Box<[_]> = self
15139 .selections
15140 .all::<usize>(&self.display_snapshot(cx))
15141 .into();
15142 if old_selections.is_empty() {
15143 return;
15144 }
15145
15146 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15147
15148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15149 let buffer = self.buffer.read(cx).snapshot(cx);
15150
15151 let mut selected_larger_node = false;
15152 let mut new_selections = old_selections
15153 .iter()
15154 .map(|selection| {
15155 let old_range = selection.start..selection.end;
15156
15157 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15158 // manually select word at selection
15159 if ["string_content", "inline"].contains(&node.kind()) {
15160 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15161 // ignore if word is already selected
15162 if !word_range.is_empty() && old_range != word_range {
15163 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15164 // only select word if start and end point belongs to same word
15165 if word_range == last_word_range {
15166 selected_larger_node = true;
15167 return Selection {
15168 id: selection.id,
15169 start: word_range.start,
15170 end: word_range.end,
15171 goal: SelectionGoal::None,
15172 reversed: selection.reversed,
15173 };
15174 }
15175 }
15176 }
15177 }
15178
15179 let mut new_range = old_range.clone();
15180 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15181 new_range = range;
15182 if !node.is_named() {
15183 continue;
15184 }
15185 if !display_map.intersects_fold(new_range.start)
15186 && !display_map.intersects_fold(new_range.end)
15187 {
15188 break;
15189 }
15190 }
15191
15192 selected_larger_node |= new_range != old_range;
15193 Selection {
15194 id: selection.id,
15195 start: new_range.start,
15196 end: new_range.end,
15197 goal: SelectionGoal::None,
15198 reversed: selection.reversed,
15199 }
15200 })
15201 .collect::<Vec<_>>();
15202
15203 if !selected_larger_node {
15204 return; // don't put this call in the history
15205 }
15206
15207 // scroll based on transformation done to the last selection created by the user
15208 let (last_old, last_new) = old_selections
15209 .last()
15210 .zip(new_selections.last().cloned())
15211 .expect("old_selections isn't empty");
15212
15213 // revert selection
15214 let is_selection_reversed = {
15215 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15216 new_selections.last_mut().expect("checked above").reversed =
15217 should_newest_selection_be_reversed;
15218 should_newest_selection_be_reversed
15219 };
15220
15221 if selected_larger_node {
15222 self.select_syntax_node_history.disable_clearing = true;
15223 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15224 s.select(new_selections.clone());
15225 });
15226 self.select_syntax_node_history.disable_clearing = false;
15227 }
15228
15229 let start_row = last_new.start.to_display_point(&display_map).row().0;
15230 let end_row = last_new.end.to_display_point(&display_map).row().0;
15231 let selection_height = end_row - start_row + 1;
15232 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15233
15234 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15235 let scroll_behavior = if fits_on_the_screen {
15236 self.request_autoscroll(Autoscroll::fit(), cx);
15237 SelectSyntaxNodeScrollBehavior::FitSelection
15238 } else if is_selection_reversed {
15239 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15240 SelectSyntaxNodeScrollBehavior::CursorTop
15241 } else {
15242 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15243 SelectSyntaxNodeScrollBehavior::CursorBottom
15244 };
15245
15246 self.select_syntax_node_history.push((
15247 old_selections,
15248 scroll_behavior,
15249 is_selection_reversed,
15250 ));
15251 }
15252
15253 pub fn select_smaller_syntax_node(
15254 &mut self,
15255 _: &SelectSmallerSyntaxNode,
15256 window: &mut Window,
15257 cx: &mut Context<Self>,
15258 ) {
15259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15260
15261 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15262 self.select_syntax_node_history.pop()
15263 {
15264 if let Some(selection) = selections.last_mut() {
15265 selection.reversed = is_selection_reversed;
15266 }
15267
15268 self.select_syntax_node_history.disable_clearing = true;
15269 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15270 s.select(selections.to_vec());
15271 });
15272 self.select_syntax_node_history.disable_clearing = false;
15273
15274 match scroll_behavior {
15275 SelectSyntaxNodeScrollBehavior::CursorTop => {
15276 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15277 }
15278 SelectSyntaxNodeScrollBehavior::FitSelection => {
15279 self.request_autoscroll(Autoscroll::fit(), cx);
15280 }
15281 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15282 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15283 }
15284 }
15285 }
15286 }
15287
15288 pub fn unwrap_syntax_node(
15289 &mut self,
15290 _: &UnwrapSyntaxNode,
15291 window: &mut Window,
15292 cx: &mut Context<Self>,
15293 ) {
15294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15295
15296 let buffer = self.buffer.read(cx).snapshot(cx);
15297 let selections = self
15298 .selections
15299 .all::<usize>(&self.display_snapshot(cx))
15300 .into_iter()
15301 // subtracting the offset requires sorting
15302 .sorted_by_key(|i| i.start);
15303
15304 let full_edits = selections
15305 .into_iter()
15306 .filter_map(|selection| {
15307 let child = if selection.is_empty()
15308 && let Some((_, ancestor_range)) =
15309 buffer.syntax_ancestor(selection.start..selection.end)
15310 {
15311 ancestor_range
15312 } else {
15313 selection.range()
15314 };
15315
15316 let mut parent = child.clone();
15317 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15318 parent = ancestor_range;
15319 if parent.start < child.start || parent.end > child.end {
15320 break;
15321 }
15322 }
15323
15324 if parent == child {
15325 return None;
15326 }
15327 let text = buffer.text_for_range(child).collect::<String>();
15328 Some((selection.id, parent, text))
15329 })
15330 .collect::<Vec<_>>();
15331 if full_edits.is_empty() {
15332 return;
15333 }
15334
15335 self.transact(window, cx, |this, window, cx| {
15336 this.buffer.update(cx, |buffer, cx| {
15337 buffer.edit(
15338 full_edits
15339 .iter()
15340 .map(|(_, p, t)| (p.clone(), t.clone()))
15341 .collect::<Vec<_>>(),
15342 None,
15343 cx,
15344 );
15345 });
15346 this.change_selections(Default::default(), window, cx, |s| {
15347 let mut offset = 0;
15348 let mut selections = vec![];
15349 for (id, parent, text) in full_edits {
15350 let start = parent.start - offset;
15351 offset += parent.len() - text.len();
15352 selections.push(Selection {
15353 id,
15354 start,
15355 end: start + text.len(),
15356 reversed: false,
15357 goal: Default::default(),
15358 });
15359 }
15360 s.select(selections);
15361 });
15362 });
15363 }
15364
15365 pub fn select_next_syntax_node(
15366 &mut self,
15367 _: &SelectNextSyntaxNode,
15368 window: &mut Window,
15369 cx: &mut Context<Self>,
15370 ) {
15371 let old_selections: Box<[_]> = self
15372 .selections
15373 .all::<usize>(&self.display_snapshot(cx))
15374 .into();
15375 if old_selections.is_empty() {
15376 return;
15377 }
15378
15379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15380
15381 let buffer = self.buffer.read(cx).snapshot(cx);
15382 let mut selected_sibling = false;
15383
15384 let new_selections = old_selections
15385 .iter()
15386 .map(|selection| {
15387 let old_range = selection.start..selection.end;
15388
15389 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15390 let new_range = node.byte_range();
15391 selected_sibling = true;
15392 Selection {
15393 id: selection.id,
15394 start: new_range.start,
15395 end: new_range.end,
15396 goal: SelectionGoal::None,
15397 reversed: selection.reversed,
15398 }
15399 } else {
15400 selection.clone()
15401 }
15402 })
15403 .collect::<Vec<_>>();
15404
15405 if selected_sibling {
15406 self.change_selections(
15407 SelectionEffects::scroll(Autoscroll::fit()),
15408 window,
15409 cx,
15410 |s| {
15411 s.select(new_selections);
15412 },
15413 );
15414 }
15415 }
15416
15417 pub fn select_prev_syntax_node(
15418 &mut self,
15419 _: &SelectPreviousSyntaxNode,
15420 window: &mut Window,
15421 cx: &mut Context<Self>,
15422 ) {
15423 let old_selections: Box<[_]> = self
15424 .selections
15425 .all::<usize>(&self.display_snapshot(cx))
15426 .into();
15427 if old_selections.is_empty() {
15428 return;
15429 }
15430
15431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15432
15433 let buffer = self.buffer.read(cx).snapshot(cx);
15434 let mut selected_sibling = false;
15435
15436 let new_selections = old_selections
15437 .iter()
15438 .map(|selection| {
15439 let old_range = selection.start..selection.end;
15440
15441 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15442 let new_range = node.byte_range();
15443 selected_sibling = true;
15444 Selection {
15445 id: selection.id,
15446 start: new_range.start,
15447 end: new_range.end,
15448 goal: SelectionGoal::None,
15449 reversed: selection.reversed,
15450 }
15451 } else {
15452 selection.clone()
15453 }
15454 })
15455 .collect::<Vec<_>>();
15456
15457 if selected_sibling {
15458 self.change_selections(
15459 SelectionEffects::scroll(Autoscroll::fit()),
15460 window,
15461 cx,
15462 |s| {
15463 s.select(new_selections);
15464 },
15465 );
15466 }
15467 }
15468
15469 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15470 if !EditorSettings::get_global(cx).gutter.runnables {
15471 self.clear_tasks();
15472 return Task::ready(());
15473 }
15474 let project = self.project().map(Entity::downgrade);
15475 let task_sources = self.lsp_task_sources(cx);
15476 let multi_buffer = self.buffer.downgrade();
15477 cx.spawn_in(window, async move |editor, cx| {
15478 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15479 let Some(project) = project.and_then(|p| p.upgrade()) else {
15480 return;
15481 };
15482 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15483 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15484 }) else {
15485 return;
15486 };
15487
15488 let hide_runnables = project
15489 .update(cx, |project, _| project.is_via_collab())
15490 .unwrap_or(true);
15491 if hide_runnables {
15492 return;
15493 }
15494 let new_rows =
15495 cx.background_spawn({
15496 let snapshot = display_snapshot.clone();
15497 async move {
15498 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15499 }
15500 })
15501 .await;
15502 let Ok(lsp_tasks) =
15503 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15504 else {
15505 return;
15506 };
15507 let lsp_tasks = lsp_tasks.await;
15508
15509 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15510 lsp_tasks
15511 .into_iter()
15512 .flat_map(|(kind, tasks)| {
15513 tasks.into_iter().filter_map(move |(location, task)| {
15514 Some((kind.clone(), location?, task))
15515 })
15516 })
15517 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15518 let buffer = location.target.buffer;
15519 let buffer_snapshot = buffer.read(cx).snapshot();
15520 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15521 |(excerpt_id, snapshot, _)| {
15522 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15523 display_snapshot
15524 .buffer_snapshot()
15525 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15526 } else {
15527 None
15528 }
15529 },
15530 );
15531 if let Some(offset) = offset {
15532 let task_buffer_range =
15533 location.target.range.to_point(&buffer_snapshot);
15534 let context_buffer_range =
15535 task_buffer_range.to_offset(&buffer_snapshot);
15536 let context_range = BufferOffset(context_buffer_range.start)
15537 ..BufferOffset(context_buffer_range.end);
15538
15539 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15540 .or_insert_with(|| RunnableTasks {
15541 templates: Vec::new(),
15542 offset,
15543 column: task_buffer_range.start.column,
15544 extra_variables: HashMap::default(),
15545 context_range,
15546 })
15547 .templates
15548 .push((kind, task.original_task().clone()));
15549 }
15550
15551 acc
15552 })
15553 }) else {
15554 return;
15555 };
15556
15557 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15558 buffer.language_settings(cx).tasks.prefer_lsp
15559 }) else {
15560 return;
15561 };
15562
15563 let rows = Self::runnable_rows(
15564 project,
15565 display_snapshot,
15566 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15567 new_rows,
15568 cx.clone(),
15569 )
15570 .await;
15571 editor
15572 .update(cx, |editor, _| {
15573 editor.clear_tasks();
15574 for (key, mut value) in rows {
15575 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15576 value.templates.extend(lsp_tasks.templates);
15577 }
15578
15579 editor.insert_tasks(key, value);
15580 }
15581 for (key, value) in lsp_tasks_by_rows {
15582 editor.insert_tasks(key, value);
15583 }
15584 })
15585 .ok();
15586 })
15587 }
15588 fn fetch_runnable_ranges(
15589 snapshot: &DisplaySnapshot,
15590 range: Range<Anchor>,
15591 ) -> Vec<language::RunnableRange> {
15592 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15593 }
15594
15595 fn runnable_rows(
15596 project: Entity<Project>,
15597 snapshot: DisplaySnapshot,
15598 prefer_lsp: bool,
15599 runnable_ranges: Vec<RunnableRange>,
15600 cx: AsyncWindowContext,
15601 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15602 cx.spawn(async move |cx| {
15603 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15604 for mut runnable in runnable_ranges {
15605 let Some(tasks) = cx
15606 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15607 .ok()
15608 else {
15609 continue;
15610 };
15611 let mut tasks = tasks.await;
15612
15613 if prefer_lsp {
15614 tasks.retain(|(task_kind, _)| {
15615 !matches!(task_kind, TaskSourceKind::Language { .. })
15616 });
15617 }
15618 if tasks.is_empty() {
15619 continue;
15620 }
15621
15622 let point = runnable
15623 .run_range
15624 .start
15625 .to_point(&snapshot.buffer_snapshot());
15626 let Some(row) = snapshot
15627 .buffer_snapshot()
15628 .buffer_line_for_row(MultiBufferRow(point.row))
15629 .map(|(_, range)| range.start.row)
15630 else {
15631 continue;
15632 };
15633
15634 let context_range =
15635 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15636 runnable_rows.push((
15637 (runnable.buffer_id, row),
15638 RunnableTasks {
15639 templates: tasks,
15640 offset: snapshot
15641 .buffer_snapshot()
15642 .anchor_before(runnable.run_range.start),
15643 context_range,
15644 column: point.column,
15645 extra_variables: runnable.extra_captures,
15646 },
15647 ));
15648 }
15649 runnable_rows
15650 })
15651 }
15652
15653 fn templates_with_tags(
15654 project: &Entity<Project>,
15655 runnable: &mut Runnable,
15656 cx: &mut App,
15657 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15658 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15659 let (worktree_id, file) = project
15660 .buffer_for_id(runnable.buffer, cx)
15661 .and_then(|buffer| buffer.read(cx).file())
15662 .map(|file| (file.worktree_id(cx), file.clone()))
15663 .unzip();
15664
15665 (
15666 project.task_store().read(cx).task_inventory().cloned(),
15667 worktree_id,
15668 file,
15669 )
15670 });
15671
15672 let tags = mem::take(&mut runnable.tags);
15673 let language = runnable.language.clone();
15674 cx.spawn(async move |cx| {
15675 let mut templates_with_tags = Vec::new();
15676 if let Some(inventory) = inventory {
15677 for RunnableTag(tag) in tags {
15678 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15679 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15680 }) else {
15681 return templates_with_tags;
15682 };
15683 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15684 move |(_, template)| {
15685 template.tags.iter().any(|source_tag| source_tag == &tag)
15686 },
15687 ));
15688 }
15689 }
15690 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15691
15692 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15693 // Strongest source wins; if we have worktree tag binding, prefer that to
15694 // global and language bindings;
15695 // if we have a global binding, prefer that to language binding.
15696 let first_mismatch = templates_with_tags
15697 .iter()
15698 .position(|(tag_source, _)| tag_source != leading_tag_source);
15699 if let Some(index) = first_mismatch {
15700 templates_with_tags.truncate(index);
15701 }
15702 }
15703
15704 templates_with_tags
15705 })
15706 }
15707
15708 pub fn move_to_enclosing_bracket(
15709 &mut self,
15710 _: &MoveToEnclosingBracket,
15711 window: &mut Window,
15712 cx: &mut Context<Self>,
15713 ) {
15714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15715 self.change_selections(Default::default(), window, cx, |s| {
15716 s.move_offsets_with(|snapshot, selection| {
15717 let Some(enclosing_bracket_ranges) =
15718 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15719 else {
15720 return;
15721 };
15722
15723 let mut best_length = usize::MAX;
15724 let mut best_inside = false;
15725 let mut best_in_bracket_range = false;
15726 let mut best_destination = None;
15727 for (open, close) in enclosing_bracket_ranges {
15728 let close = close.to_inclusive();
15729 let length = close.end() - open.start;
15730 let inside = selection.start >= open.end && selection.end <= *close.start();
15731 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15732 || close.contains(&selection.head());
15733
15734 // If best is next to a bracket and current isn't, skip
15735 if !in_bracket_range && best_in_bracket_range {
15736 continue;
15737 }
15738
15739 // Prefer smaller lengths unless best is inside and current isn't
15740 if length > best_length && (best_inside || !inside) {
15741 continue;
15742 }
15743
15744 best_length = length;
15745 best_inside = inside;
15746 best_in_bracket_range = in_bracket_range;
15747 best_destination = Some(
15748 if close.contains(&selection.start) && close.contains(&selection.end) {
15749 if inside { open.end } else { open.start }
15750 } else if inside {
15751 *close.start()
15752 } else {
15753 *close.end()
15754 },
15755 );
15756 }
15757
15758 if let Some(destination) = best_destination {
15759 selection.collapse_to(destination, SelectionGoal::None);
15760 }
15761 })
15762 });
15763 }
15764
15765 pub fn undo_selection(
15766 &mut self,
15767 _: &UndoSelection,
15768 window: &mut Window,
15769 cx: &mut Context<Self>,
15770 ) {
15771 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15772 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15773 self.selection_history.mode = SelectionHistoryMode::Undoing;
15774 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15775 this.end_selection(window, cx);
15776 this.change_selections(
15777 SelectionEffects::scroll(Autoscroll::newest()),
15778 window,
15779 cx,
15780 |s| s.select_anchors(entry.selections.to_vec()),
15781 );
15782 });
15783 self.selection_history.mode = SelectionHistoryMode::Normal;
15784
15785 self.select_next_state = entry.select_next_state;
15786 self.select_prev_state = entry.select_prev_state;
15787 self.add_selections_state = entry.add_selections_state;
15788 }
15789 }
15790
15791 pub fn redo_selection(
15792 &mut self,
15793 _: &RedoSelection,
15794 window: &mut Window,
15795 cx: &mut Context<Self>,
15796 ) {
15797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15798 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15799 self.selection_history.mode = SelectionHistoryMode::Redoing;
15800 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15801 this.end_selection(window, cx);
15802 this.change_selections(
15803 SelectionEffects::scroll(Autoscroll::newest()),
15804 window,
15805 cx,
15806 |s| s.select_anchors(entry.selections.to_vec()),
15807 );
15808 });
15809 self.selection_history.mode = SelectionHistoryMode::Normal;
15810
15811 self.select_next_state = entry.select_next_state;
15812 self.select_prev_state = entry.select_prev_state;
15813 self.add_selections_state = entry.add_selections_state;
15814 }
15815 }
15816
15817 pub fn expand_excerpts(
15818 &mut self,
15819 action: &ExpandExcerpts,
15820 _: &mut Window,
15821 cx: &mut Context<Self>,
15822 ) {
15823 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15824 }
15825
15826 pub fn expand_excerpts_down(
15827 &mut self,
15828 action: &ExpandExcerptsDown,
15829 _: &mut Window,
15830 cx: &mut Context<Self>,
15831 ) {
15832 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15833 }
15834
15835 pub fn expand_excerpts_up(
15836 &mut self,
15837 action: &ExpandExcerptsUp,
15838 _: &mut Window,
15839 cx: &mut Context<Self>,
15840 ) {
15841 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15842 }
15843
15844 pub fn expand_excerpts_for_direction(
15845 &mut self,
15846 lines: u32,
15847 direction: ExpandExcerptDirection,
15848
15849 cx: &mut Context<Self>,
15850 ) {
15851 let selections = self.selections.disjoint_anchors_arc();
15852
15853 let lines = if lines == 0 {
15854 EditorSettings::get_global(cx).expand_excerpt_lines
15855 } else {
15856 lines
15857 };
15858
15859 self.buffer.update(cx, |buffer, cx| {
15860 let snapshot = buffer.snapshot(cx);
15861 let mut excerpt_ids = selections
15862 .iter()
15863 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15864 .collect::<Vec<_>>();
15865 excerpt_ids.sort();
15866 excerpt_ids.dedup();
15867 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15868 })
15869 }
15870
15871 pub fn expand_excerpt(
15872 &mut self,
15873 excerpt: ExcerptId,
15874 direction: ExpandExcerptDirection,
15875 window: &mut Window,
15876 cx: &mut Context<Self>,
15877 ) {
15878 let current_scroll_position = self.scroll_position(cx);
15879 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15880 let mut scroll = None;
15881
15882 if direction == ExpandExcerptDirection::Down {
15883 let multi_buffer = self.buffer.read(cx);
15884 let snapshot = multi_buffer.snapshot(cx);
15885 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15886 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15887 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15888 {
15889 let buffer_snapshot = buffer.read(cx).snapshot();
15890 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15891 let last_row = buffer_snapshot.max_point().row;
15892 let lines_below = last_row.saturating_sub(excerpt_end_row);
15893 if lines_below >= lines_to_expand {
15894 scroll = Some(
15895 current_scroll_position
15896 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
15897 );
15898 }
15899 }
15900 }
15901 if direction == ExpandExcerptDirection::Up
15902 && self
15903 .buffer
15904 .read(cx)
15905 .snapshot(cx)
15906 .excerpt_before(excerpt)
15907 .is_none()
15908 {
15909 scroll = Some(current_scroll_position);
15910 }
15911
15912 self.buffer.update(cx, |buffer, cx| {
15913 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15914 });
15915
15916 if let Some(new_scroll_position) = scroll {
15917 self.set_scroll_position(new_scroll_position, window, cx);
15918 }
15919 }
15920
15921 pub fn go_to_singleton_buffer_point(
15922 &mut self,
15923 point: Point,
15924 window: &mut Window,
15925 cx: &mut Context<Self>,
15926 ) {
15927 self.go_to_singleton_buffer_range(point..point, window, cx);
15928 }
15929
15930 pub fn go_to_singleton_buffer_range(
15931 &mut self,
15932 range: Range<Point>,
15933 window: &mut Window,
15934 cx: &mut Context<Self>,
15935 ) {
15936 let multibuffer = self.buffer().read(cx);
15937 let Some(buffer) = multibuffer.as_singleton() else {
15938 return;
15939 };
15940 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15941 return;
15942 };
15943 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15944 return;
15945 };
15946 self.change_selections(
15947 SelectionEffects::default().nav_history(true),
15948 window,
15949 cx,
15950 |s| s.select_anchor_ranges([start..end]),
15951 );
15952 }
15953
15954 pub fn go_to_diagnostic(
15955 &mut self,
15956 action: &GoToDiagnostic,
15957 window: &mut Window,
15958 cx: &mut Context<Self>,
15959 ) {
15960 if !self.diagnostics_enabled() {
15961 return;
15962 }
15963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15964 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15965 }
15966
15967 pub fn go_to_prev_diagnostic(
15968 &mut self,
15969 action: &GoToPreviousDiagnostic,
15970 window: &mut Window,
15971 cx: &mut Context<Self>,
15972 ) {
15973 if !self.diagnostics_enabled() {
15974 return;
15975 }
15976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15977 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15978 }
15979
15980 pub fn go_to_diagnostic_impl(
15981 &mut self,
15982 direction: Direction,
15983 severity: GoToDiagnosticSeverityFilter,
15984 window: &mut Window,
15985 cx: &mut Context<Self>,
15986 ) {
15987 let buffer = self.buffer.read(cx).snapshot(cx);
15988 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15989
15990 let mut active_group_id = None;
15991 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15992 && active_group.active_range.start.to_offset(&buffer) == selection.start
15993 {
15994 active_group_id = Some(active_group.group_id);
15995 }
15996
15997 fn filtered<'a>(
15998 snapshot: EditorSnapshot,
15999 severity: GoToDiagnosticSeverityFilter,
16000 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16001 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16002 diagnostics
16003 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16004 .filter(|entry| entry.range.start != entry.range.end)
16005 .filter(|entry| !entry.diagnostic.is_unnecessary)
16006 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16007 }
16008
16009 let snapshot = self.snapshot(window, cx);
16010 let before = filtered(
16011 snapshot.clone(),
16012 severity,
16013 buffer
16014 .diagnostics_in_range(0..selection.start)
16015 .filter(|entry| entry.range.start <= selection.start),
16016 );
16017 let after = filtered(
16018 snapshot,
16019 severity,
16020 buffer
16021 .diagnostics_in_range(selection.start..buffer.len())
16022 .filter(|entry| entry.range.start >= selection.start),
16023 );
16024
16025 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16026 if direction == Direction::Prev {
16027 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16028 {
16029 for diagnostic in prev_diagnostics.into_iter().rev() {
16030 if diagnostic.range.start != selection.start
16031 || active_group_id
16032 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16033 {
16034 found = Some(diagnostic);
16035 break 'outer;
16036 }
16037 }
16038 }
16039 } else {
16040 for diagnostic in after.chain(before) {
16041 if diagnostic.range.start != selection.start
16042 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16043 {
16044 found = Some(diagnostic);
16045 break;
16046 }
16047 }
16048 }
16049 let Some(next_diagnostic) = found else {
16050 return;
16051 };
16052
16053 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16054 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16055 return;
16056 };
16057 self.change_selections(Default::default(), window, cx, |s| {
16058 s.select_ranges(vec![
16059 next_diagnostic.range.start..next_diagnostic.range.start,
16060 ])
16061 });
16062 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16063 self.refresh_edit_prediction(false, true, window, cx);
16064 }
16065
16066 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16067 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16068 let snapshot = self.snapshot(window, cx);
16069 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16070 self.go_to_hunk_before_or_after_position(
16071 &snapshot,
16072 selection.head(),
16073 Direction::Next,
16074 window,
16075 cx,
16076 );
16077 }
16078
16079 pub fn go_to_hunk_before_or_after_position(
16080 &mut self,
16081 snapshot: &EditorSnapshot,
16082 position: Point,
16083 direction: Direction,
16084 window: &mut Window,
16085 cx: &mut Context<Editor>,
16086 ) {
16087 let row = if direction == Direction::Next {
16088 self.hunk_after_position(snapshot, position)
16089 .map(|hunk| hunk.row_range.start)
16090 } else {
16091 self.hunk_before_position(snapshot, position)
16092 };
16093
16094 if let Some(row) = row {
16095 let destination = Point::new(row.0, 0);
16096 let autoscroll = Autoscroll::center();
16097
16098 self.unfold_ranges(&[destination..destination], false, false, cx);
16099 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16100 s.select_ranges([destination..destination]);
16101 });
16102 }
16103 }
16104
16105 fn hunk_after_position(
16106 &mut self,
16107 snapshot: &EditorSnapshot,
16108 position: Point,
16109 ) -> Option<MultiBufferDiffHunk> {
16110 snapshot
16111 .buffer_snapshot()
16112 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16113 .find(|hunk| hunk.row_range.start.0 > position.row)
16114 .or_else(|| {
16115 snapshot
16116 .buffer_snapshot()
16117 .diff_hunks_in_range(Point::zero()..position)
16118 .find(|hunk| hunk.row_range.end.0 < position.row)
16119 })
16120 }
16121
16122 fn go_to_prev_hunk(
16123 &mut self,
16124 _: &GoToPreviousHunk,
16125 window: &mut Window,
16126 cx: &mut Context<Self>,
16127 ) {
16128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16129 let snapshot = self.snapshot(window, cx);
16130 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16131 self.go_to_hunk_before_or_after_position(
16132 &snapshot,
16133 selection.head(),
16134 Direction::Prev,
16135 window,
16136 cx,
16137 );
16138 }
16139
16140 fn hunk_before_position(
16141 &mut self,
16142 snapshot: &EditorSnapshot,
16143 position: Point,
16144 ) -> Option<MultiBufferRow> {
16145 snapshot
16146 .buffer_snapshot()
16147 .diff_hunk_before(position)
16148 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16149 }
16150
16151 fn go_to_next_change(
16152 &mut self,
16153 _: &GoToNextChange,
16154 window: &mut Window,
16155 cx: &mut Context<Self>,
16156 ) {
16157 if let Some(selections) = self
16158 .change_list
16159 .next_change(1, Direction::Next)
16160 .map(|s| s.to_vec())
16161 {
16162 self.change_selections(Default::default(), window, cx, |s| {
16163 let map = s.display_map();
16164 s.select_display_ranges(selections.iter().map(|a| {
16165 let point = a.to_display_point(&map);
16166 point..point
16167 }))
16168 })
16169 }
16170 }
16171
16172 fn go_to_previous_change(
16173 &mut self,
16174 _: &GoToPreviousChange,
16175 window: &mut Window,
16176 cx: &mut Context<Self>,
16177 ) {
16178 if let Some(selections) = self
16179 .change_list
16180 .next_change(1, Direction::Prev)
16181 .map(|s| s.to_vec())
16182 {
16183 self.change_selections(Default::default(), window, cx, |s| {
16184 let map = s.display_map();
16185 s.select_display_ranges(selections.iter().map(|a| {
16186 let point = a.to_display_point(&map);
16187 point..point
16188 }))
16189 })
16190 }
16191 }
16192
16193 pub fn go_to_next_document_highlight(
16194 &mut self,
16195 _: &GoToNextDocumentHighlight,
16196 window: &mut Window,
16197 cx: &mut Context<Self>,
16198 ) {
16199 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16200 }
16201
16202 pub fn go_to_prev_document_highlight(
16203 &mut self,
16204 _: &GoToPreviousDocumentHighlight,
16205 window: &mut Window,
16206 cx: &mut Context<Self>,
16207 ) {
16208 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16209 }
16210
16211 pub fn go_to_document_highlight_before_or_after_position(
16212 &mut self,
16213 direction: Direction,
16214 window: &mut Window,
16215 cx: &mut Context<Editor>,
16216 ) {
16217 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16218 let snapshot = self.snapshot(window, cx);
16219 let buffer = &snapshot.buffer_snapshot();
16220 let position = self
16221 .selections
16222 .newest::<Point>(&snapshot.display_snapshot)
16223 .head();
16224 let anchor_position = buffer.anchor_after(position);
16225
16226 // Get all document highlights (both read and write)
16227 let mut all_highlights = Vec::new();
16228
16229 if let Some((_, read_highlights)) = self
16230 .background_highlights
16231 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16232 {
16233 all_highlights.extend(read_highlights.iter());
16234 }
16235
16236 if let Some((_, write_highlights)) = self
16237 .background_highlights
16238 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16239 {
16240 all_highlights.extend(write_highlights.iter());
16241 }
16242
16243 if all_highlights.is_empty() {
16244 return;
16245 }
16246
16247 // Sort highlights by position
16248 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16249
16250 let target_highlight = match direction {
16251 Direction::Next => {
16252 // Find the first highlight after the current position
16253 all_highlights
16254 .iter()
16255 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16256 }
16257 Direction::Prev => {
16258 // Find the last highlight before the current position
16259 all_highlights
16260 .iter()
16261 .rev()
16262 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16263 }
16264 };
16265
16266 if let Some(highlight) = target_highlight {
16267 let destination = highlight.start.to_point(buffer);
16268 let autoscroll = Autoscroll::center();
16269
16270 self.unfold_ranges(&[destination..destination], false, false, cx);
16271 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16272 s.select_ranges([destination..destination]);
16273 });
16274 }
16275 }
16276
16277 fn go_to_line<T: 'static>(
16278 &mut self,
16279 position: Anchor,
16280 highlight_color: Option<Hsla>,
16281 window: &mut Window,
16282 cx: &mut Context<Self>,
16283 ) {
16284 let snapshot = self.snapshot(window, cx).display_snapshot;
16285 let position = position.to_point(&snapshot.buffer_snapshot());
16286 let start = snapshot
16287 .buffer_snapshot()
16288 .clip_point(Point::new(position.row, 0), Bias::Left);
16289 let end = start + Point::new(1, 0);
16290 let start = snapshot.buffer_snapshot().anchor_before(start);
16291 let end = snapshot.buffer_snapshot().anchor_before(end);
16292
16293 self.highlight_rows::<T>(
16294 start..end,
16295 highlight_color
16296 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16297 Default::default(),
16298 cx,
16299 );
16300
16301 if self.buffer.read(cx).is_singleton() {
16302 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16303 }
16304 }
16305
16306 pub fn go_to_definition(
16307 &mut self,
16308 _: &GoToDefinition,
16309 window: &mut Window,
16310 cx: &mut Context<Self>,
16311 ) -> Task<Result<Navigated>> {
16312 let definition =
16313 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16314 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16315 cx.spawn_in(window, async move |editor, cx| {
16316 if definition.await? == Navigated::Yes {
16317 return Ok(Navigated::Yes);
16318 }
16319 match fallback_strategy {
16320 GoToDefinitionFallback::None => Ok(Navigated::No),
16321 GoToDefinitionFallback::FindAllReferences => {
16322 match editor.update_in(cx, |editor, window, cx| {
16323 editor.find_all_references(&FindAllReferences, window, cx)
16324 })? {
16325 Some(references) => references.await,
16326 None => Ok(Navigated::No),
16327 }
16328 }
16329 }
16330 })
16331 }
16332
16333 pub fn go_to_declaration(
16334 &mut self,
16335 _: &GoToDeclaration,
16336 window: &mut Window,
16337 cx: &mut Context<Self>,
16338 ) -> Task<Result<Navigated>> {
16339 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16340 }
16341
16342 pub fn go_to_declaration_split(
16343 &mut self,
16344 _: &GoToDeclaration,
16345 window: &mut Window,
16346 cx: &mut Context<Self>,
16347 ) -> Task<Result<Navigated>> {
16348 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16349 }
16350
16351 pub fn go_to_implementation(
16352 &mut self,
16353 _: &GoToImplementation,
16354 window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) -> Task<Result<Navigated>> {
16357 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16358 }
16359
16360 pub fn go_to_implementation_split(
16361 &mut self,
16362 _: &GoToImplementationSplit,
16363 window: &mut Window,
16364 cx: &mut Context<Self>,
16365 ) -> Task<Result<Navigated>> {
16366 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16367 }
16368
16369 pub fn go_to_type_definition(
16370 &mut self,
16371 _: &GoToTypeDefinition,
16372 window: &mut Window,
16373 cx: &mut Context<Self>,
16374 ) -> Task<Result<Navigated>> {
16375 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16376 }
16377
16378 pub fn go_to_definition_split(
16379 &mut self,
16380 _: &GoToDefinitionSplit,
16381 window: &mut Window,
16382 cx: &mut Context<Self>,
16383 ) -> Task<Result<Navigated>> {
16384 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16385 }
16386
16387 pub fn go_to_type_definition_split(
16388 &mut self,
16389 _: &GoToTypeDefinitionSplit,
16390 window: &mut Window,
16391 cx: &mut Context<Self>,
16392 ) -> Task<Result<Navigated>> {
16393 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16394 }
16395
16396 fn go_to_definition_of_kind(
16397 &mut self,
16398 kind: GotoDefinitionKind,
16399 split: bool,
16400 window: &mut Window,
16401 cx: &mut Context<Self>,
16402 ) -> Task<Result<Navigated>> {
16403 let Some(provider) = self.semantics_provider.clone() else {
16404 return Task::ready(Ok(Navigated::No));
16405 };
16406 let head = self
16407 .selections
16408 .newest::<usize>(&self.display_snapshot(cx))
16409 .head();
16410 let buffer = self.buffer.read(cx);
16411 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16412 return Task::ready(Ok(Navigated::No));
16413 };
16414 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16415 return Task::ready(Ok(Navigated::No));
16416 };
16417
16418 cx.spawn_in(window, async move |editor, cx| {
16419 let Some(definitions) = definitions.await? else {
16420 return Ok(Navigated::No);
16421 };
16422 let navigated = editor
16423 .update_in(cx, |editor, window, cx| {
16424 editor.navigate_to_hover_links(
16425 Some(kind),
16426 definitions
16427 .into_iter()
16428 .filter(|location| {
16429 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16430 })
16431 .map(HoverLink::Text)
16432 .collect::<Vec<_>>(),
16433 split,
16434 window,
16435 cx,
16436 )
16437 })?
16438 .await?;
16439 anyhow::Ok(navigated)
16440 })
16441 }
16442
16443 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16444 let selection = self.selections.newest_anchor();
16445 let head = selection.head();
16446 let tail = selection.tail();
16447
16448 let Some((buffer, start_position)) =
16449 self.buffer.read(cx).text_anchor_for_position(head, cx)
16450 else {
16451 return;
16452 };
16453
16454 let end_position = if head != tail {
16455 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16456 return;
16457 };
16458 Some(pos)
16459 } else {
16460 None
16461 };
16462
16463 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16464 let url = if let Some(end_pos) = end_position {
16465 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16466 } else {
16467 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16468 };
16469
16470 if let Some(url) = url {
16471 cx.update(|window, cx| {
16472 if parse_zed_link(&url, cx).is_some() {
16473 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16474 } else {
16475 cx.open_url(&url);
16476 }
16477 })?;
16478 }
16479
16480 anyhow::Ok(())
16481 });
16482
16483 url_finder.detach();
16484 }
16485
16486 pub fn open_selected_filename(
16487 &mut self,
16488 _: &OpenSelectedFilename,
16489 window: &mut Window,
16490 cx: &mut Context<Self>,
16491 ) {
16492 let Some(workspace) = self.workspace() else {
16493 return;
16494 };
16495
16496 let position = self.selections.newest_anchor().head();
16497
16498 let Some((buffer, buffer_position)) =
16499 self.buffer.read(cx).text_anchor_for_position(position, cx)
16500 else {
16501 return;
16502 };
16503
16504 let project = self.project.clone();
16505
16506 cx.spawn_in(window, async move |_, cx| {
16507 let result = find_file(&buffer, project, buffer_position, cx).await;
16508
16509 if let Some((_, path)) = result {
16510 workspace
16511 .update_in(cx, |workspace, window, cx| {
16512 workspace.open_resolved_path(path, window, cx)
16513 })?
16514 .await?;
16515 }
16516 anyhow::Ok(())
16517 })
16518 .detach();
16519 }
16520
16521 pub(crate) fn navigate_to_hover_links(
16522 &mut self,
16523 kind: Option<GotoDefinitionKind>,
16524 definitions: Vec<HoverLink>,
16525 split: bool,
16526 window: &mut Window,
16527 cx: &mut Context<Editor>,
16528 ) -> Task<Result<Navigated>> {
16529 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16530 let mut first_url_or_file = None;
16531 let definitions: Vec<_> = definitions
16532 .into_iter()
16533 .filter_map(|def| match def {
16534 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16535 HoverLink::InlayHint(lsp_location, server_id) => {
16536 let computation =
16537 self.compute_target_location(lsp_location, server_id, window, cx);
16538 Some(cx.background_spawn(computation))
16539 }
16540 HoverLink::Url(url) => {
16541 first_url_or_file = Some(Either::Left(url));
16542 None
16543 }
16544 HoverLink::File(path) => {
16545 first_url_or_file = Some(Either::Right(path));
16546 None
16547 }
16548 })
16549 .collect();
16550
16551 let workspace = self.workspace();
16552
16553 cx.spawn_in(window, async move |editor, cx| {
16554 let locations: Vec<Location> = future::join_all(definitions)
16555 .await
16556 .into_iter()
16557 .filter_map(|location| location.transpose())
16558 .collect::<Result<_>>()
16559 .context("location tasks")?;
16560 let mut locations = cx.update(|_, cx| {
16561 locations
16562 .into_iter()
16563 .map(|location| {
16564 let buffer = location.buffer.read(cx);
16565 (location.buffer, location.range.to_point(buffer))
16566 })
16567 .into_group_map()
16568 })?;
16569 let mut num_locations = 0;
16570 for ranges in locations.values_mut() {
16571 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16572 ranges.dedup();
16573 num_locations += ranges.len();
16574 }
16575
16576 if num_locations > 1 {
16577 let Some(workspace) = workspace else {
16578 return Ok(Navigated::No);
16579 };
16580
16581 let tab_kind = match kind {
16582 Some(GotoDefinitionKind::Implementation) => "Implementations",
16583 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16584 Some(GotoDefinitionKind::Declaration) => "Declarations",
16585 Some(GotoDefinitionKind::Type) => "Types",
16586 };
16587 let title = editor
16588 .update_in(cx, |_, _, cx| {
16589 let target = locations
16590 .iter()
16591 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16592 .map(|(buffer, location)| {
16593 buffer
16594 .read(cx)
16595 .text_for_range(location.clone())
16596 .collect::<String>()
16597 })
16598 .filter(|text| !text.contains('\n'))
16599 .unique()
16600 .take(3)
16601 .join(", ");
16602 if target.is_empty() {
16603 tab_kind.to_owned()
16604 } else {
16605 format!("{tab_kind} for {target}")
16606 }
16607 })
16608 .context("buffer title")?;
16609
16610 let opened = workspace
16611 .update_in(cx, |workspace, window, cx| {
16612 Self::open_locations_in_multibuffer(
16613 workspace,
16614 locations,
16615 title,
16616 split,
16617 MultibufferSelectionMode::First,
16618 window,
16619 cx,
16620 )
16621 })
16622 .is_ok();
16623
16624 anyhow::Ok(Navigated::from_bool(opened))
16625 } else if num_locations == 0 {
16626 // If there is one url or file, open it directly
16627 match first_url_or_file {
16628 Some(Either::Left(url)) => {
16629 cx.update(|_, cx| cx.open_url(&url))?;
16630 Ok(Navigated::Yes)
16631 }
16632 Some(Either::Right(path)) => {
16633 let Some(workspace) = workspace else {
16634 return Ok(Navigated::No);
16635 };
16636
16637 workspace
16638 .update_in(cx, |workspace, window, cx| {
16639 workspace.open_resolved_path(path, window, cx)
16640 })?
16641 .await?;
16642 Ok(Navigated::Yes)
16643 }
16644 None => Ok(Navigated::No),
16645 }
16646 } else {
16647 let Some(workspace) = workspace else {
16648 return Ok(Navigated::No);
16649 };
16650
16651 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16652 let target_range = target_ranges.first().unwrap().clone();
16653
16654 editor.update_in(cx, |editor, window, cx| {
16655 let range = target_range.to_point(target_buffer.read(cx));
16656 let range = editor.range_for_match(&range, false);
16657 let range = collapse_multiline_range(range);
16658
16659 if !split
16660 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16661 {
16662 editor.go_to_singleton_buffer_range(range, window, cx);
16663 } else {
16664 let pane = workspace.read(cx).active_pane().clone();
16665 window.defer(cx, move |window, cx| {
16666 let target_editor: Entity<Self> =
16667 workspace.update(cx, |workspace, cx| {
16668 let pane = if split {
16669 workspace.adjacent_pane(window, cx)
16670 } else {
16671 workspace.active_pane().clone()
16672 };
16673
16674 workspace.open_project_item(
16675 pane,
16676 target_buffer.clone(),
16677 true,
16678 true,
16679 window,
16680 cx,
16681 )
16682 });
16683 target_editor.update(cx, |target_editor, cx| {
16684 // When selecting a definition in a different buffer, disable the nav history
16685 // to avoid creating a history entry at the previous cursor location.
16686 pane.update(cx, |pane, _| pane.disable_history());
16687 target_editor.go_to_singleton_buffer_range(range, window, cx);
16688 pane.update(cx, |pane, _| pane.enable_history());
16689 });
16690 });
16691 }
16692 Navigated::Yes
16693 })
16694 }
16695 })
16696 }
16697
16698 fn compute_target_location(
16699 &self,
16700 lsp_location: lsp::Location,
16701 server_id: LanguageServerId,
16702 window: &mut Window,
16703 cx: &mut Context<Self>,
16704 ) -> Task<anyhow::Result<Option<Location>>> {
16705 let Some(project) = self.project.clone() else {
16706 return Task::ready(Ok(None));
16707 };
16708
16709 cx.spawn_in(window, async move |editor, cx| {
16710 let location_task = editor.update(cx, |_, cx| {
16711 project.update(cx, |project, cx| {
16712 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16713 })
16714 })?;
16715 let location = Some({
16716 let target_buffer_handle = location_task.await.context("open local buffer")?;
16717 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16718 let target_start = target_buffer
16719 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16720 let target_end = target_buffer
16721 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16722 target_buffer.anchor_after(target_start)
16723 ..target_buffer.anchor_before(target_end)
16724 })?;
16725 Location {
16726 buffer: target_buffer_handle,
16727 range,
16728 }
16729 });
16730 Ok(location)
16731 })
16732 }
16733
16734 fn go_to_next_reference(
16735 &mut self,
16736 _: &GoToNextReference,
16737 window: &mut Window,
16738 cx: &mut Context<Self>,
16739 ) {
16740 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16741 if let Some(task) = task {
16742 task.detach();
16743 };
16744 }
16745
16746 fn go_to_prev_reference(
16747 &mut self,
16748 _: &GoToPreviousReference,
16749 window: &mut Window,
16750 cx: &mut Context<Self>,
16751 ) {
16752 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16753 if let Some(task) = task {
16754 task.detach();
16755 };
16756 }
16757
16758 pub fn go_to_reference_before_or_after_position(
16759 &mut self,
16760 direction: Direction,
16761 count: usize,
16762 window: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) -> Option<Task<Result<()>>> {
16765 let selection = self.selections.newest_anchor();
16766 let head = selection.head();
16767
16768 let multi_buffer = self.buffer.read(cx);
16769
16770 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16771 let workspace = self.workspace()?;
16772 let project = workspace.read(cx).project().clone();
16773 let references =
16774 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16775 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16776 let Some(locations) = references.await? else {
16777 return Ok(());
16778 };
16779
16780 if locations.is_empty() {
16781 // totally normal - the cursor may be on something which is not
16782 // a symbol (e.g. a keyword)
16783 log::info!("no references found under cursor");
16784 return Ok(());
16785 }
16786
16787 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16788
16789 let multi_buffer_snapshot =
16790 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16791
16792 let (locations, current_location_index) =
16793 multi_buffer.update(cx, |multi_buffer, cx| {
16794 let mut locations = locations
16795 .into_iter()
16796 .filter_map(|loc| {
16797 let start = multi_buffer.buffer_anchor_to_anchor(
16798 &loc.buffer,
16799 loc.range.start,
16800 cx,
16801 )?;
16802 let end = multi_buffer.buffer_anchor_to_anchor(
16803 &loc.buffer,
16804 loc.range.end,
16805 cx,
16806 )?;
16807 Some(start..end)
16808 })
16809 .collect::<Vec<_>>();
16810
16811 // There is an O(n) implementation, but given this list will be
16812 // small (usually <100 items), the extra O(log(n)) factor isn't
16813 // worth the (surprisingly large amount of) extra complexity.
16814 locations
16815 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16816
16817 let head_offset = head.to_offset(&multi_buffer_snapshot);
16818
16819 let current_location_index = locations.iter().position(|loc| {
16820 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16821 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16822 });
16823
16824 (locations, current_location_index)
16825 })?;
16826
16827 let Some(current_location_index) = current_location_index else {
16828 // This indicates something has gone wrong, because we already
16829 // handle the "no references" case above
16830 log::error!(
16831 "failed to find current reference under cursor. Total references: {}",
16832 locations.len()
16833 );
16834 return Ok(());
16835 };
16836
16837 let destination_location_index = match direction {
16838 Direction::Next => (current_location_index + count) % locations.len(),
16839 Direction::Prev => {
16840 (current_location_index + locations.len() - count % locations.len())
16841 % locations.len()
16842 }
16843 };
16844
16845 // TODO(cameron): is this needed?
16846 // the thinking is to avoid "jumping to the current location" (avoid
16847 // polluting "jumplist" in vim terms)
16848 if current_location_index == destination_location_index {
16849 return Ok(());
16850 }
16851
16852 let Range { start, end } = locations[destination_location_index];
16853
16854 editor.update_in(cx, |editor, window, cx| {
16855 let effects = SelectionEffects::default();
16856
16857 editor.unfold_ranges(&[start..end], false, false, cx);
16858 editor.change_selections(effects, window, cx, |s| {
16859 s.select_ranges([start..start]);
16860 });
16861 })?;
16862
16863 Ok(())
16864 }))
16865 }
16866
16867 pub fn find_all_references(
16868 &mut self,
16869 _: &FindAllReferences,
16870 window: &mut Window,
16871 cx: &mut Context<Self>,
16872 ) -> Option<Task<Result<Navigated>>> {
16873 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16874 let multi_buffer = self.buffer.read(cx);
16875 let head = selection.head();
16876
16877 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16878 let head_anchor = multi_buffer_snapshot.anchor_at(
16879 head,
16880 if head < selection.tail() {
16881 Bias::Right
16882 } else {
16883 Bias::Left
16884 },
16885 );
16886
16887 match self
16888 .find_all_references_task_sources
16889 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16890 {
16891 Ok(_) => {
16892 log::info!(
16893 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16894 );
16895 return None;
16896 }
16897 Err(i) => {
16898 self.find_all_references_task_sources.insert(i, head_anchor);
16899 }
16900 }
16901
16902 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16903 let workspace = self.workspace()?;
16904 let project = workspace.read(cx).project().clone();
16905 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16906 Some(cx.spawn_in(window, async move |editor, cx| {
16907 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16908 if let Ok(i) = editor
16909 .find_all_references_task_sources
16910 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16911 {
16912 editor.find_all_references_task_sources.remove(i);
16913 }
16914 });
16915
16916 let Some(locations) = references.await? else {
16917 return anyhow::Ok(Navigated::No);
16918 };
16919 let mut locations = cx.update(|_, cx| {
16920 locations
16921 .into_iter()
16922 .map(|location| {
16923 let buffer = location.buffer.read(cx);
16924 (location.buffer, location.range.to_point(buffer))
16925 })
16926 .into_group_map()
16927 })?;
16928 if locations.is_empty() {
16929 return anyhow::Ok(Navigated::No);
16930 }
16931 for ranges in locations.values_mut() {
16932 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16933 ranges.dedup();
16934 }
16935
16936 workspace.update_in(cx, |workspace, window, cx| {
16937 let target = locations
16938 .iter()
16939 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16940 .map(|(buffer, location)| {
16941 buffer
16942 .read(cx)
16943 .text_for_range(location.clone())
16944 .collect::<String>()
16945 })
16946 .filter(|text| !text.contains('\n'))
16947 .unique()
16948 .take(3)
16949 .join(", ");
16950 let title = if target.is_empty() {
16951 "References".to_owned()
16952 } else {
16953 format!("References to {target}")
16954 };
16955 Self::open_locations_in_multibuffer(
16956 workspace,
16957 locations,
16958 title,
16959 false,
16960 MultibufferSelectionMode::First,
16961 window,
16962 cx,
16963 );
16964 Navigated::Yes
16965 })
16966 }))
16967 }
16968
16969 /// Opens a multibuffer with the given project locations in it
16970 pub fn open_locations_in_multibuffer(
16971 workspace: &mut Workspace,
16972 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16973 title: String,
16974 split: bool,
16975 multibuffer_selection_mode: MultibufferSelectionMode,
16976 window: &mut Window,
16977 cx: &mut Context<Workspace>,
16978 ) {
16979 if locations.is_empty() {
16980 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16981 return;
16982 }
16983
16984 let capability = workspace.project().read(cx).capability();
16985 let mut ranges = <Vec<Range<Anchor>>>::new();
16986
16987 // a key to find existing multibuffer editors with the same set of locations
16988 // to prevent us from opening more and more multibuffer tabs for searches and the like
16989 let mut key = (title.clone(), vec![]);
16990 let excerpt_buffer = cx.new(|cx| {
16991 let key = &mut key.1;
16992 let mut multibuffer = MultiBuffer::new(capability);
16993 for (buffer, mut ranges_for_buffer) in locations {
16994 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16995 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16996 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16997 PathKey::for_buffer(&buffer, cx),
16998 buffer.clone(),
16999 ranges_for_buffer,
17000 multibuffer_context_lines(cx),
17001 cx,
17002 );
17003 ranges.extend(new_ranges)
17004 }
17005
17006 multibuffer.with_title(title)
17007 });
17008 let existing = workspace.active_pane().update(cx, |pane, cx| {
17009 pane.items()
17010 .filter_map(|item| item.downcast::<Editor>())
17011 .find(|editor| {
17012 editor
17013 .read(cx)
17014 .lookup_key
17015 .as_ref()
17016 .and_then(|it| {
17017 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17018 })
17019 .is_some_and(|it| *it == key)
17020 })
17021 });
17022 let editor = existing.unwrap_or_else(|| {
17023 cx.new(|cx| {
17024 let mut editor = Editor::for_multibuffer(
17025 excerpt_buffer,
17026 Some(workspace.project().clone()),
17027 window,
17028 cx,
17029 );
17030 editor.lookup_key = Some(Box::new(key));
17031 editor
17032 })
17033 });
17034 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17035 MultibufferSelectionMode::First => {
17036 if let Some(first_range) = ranges.first() {
17037 editor.change_selections(
17038 SelectionEffects::no_scroll(),
17039 window,
17040 cx,
17041 |selections| {
17042 selections.clear_disjoint();
17043 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17044 },
17045 );
17046 }
17047 editor.highlight_background::<Self>(
17048 &ranges,
17049 |theme| theme.colors().editor_highlighted_line_background,
17050 cx,
17051 );
17052 }
17053 MultibufferSelectionMode::All => {
17054 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17055 selections.clear_disjoint();
17056 selections.select_anchor_ranges(ranges);
17057 });
17058 }
17059 });
17060
17061 let item = Box::new(editor);
17062 let item_id = item.item_id();
17063
17064 if split {
17065 let pane = workspace.adjacent_pane(window, cx);
17066 workspace.add_item(pane, item, None, true, true, window, cx);
17067 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17068 let (preview_item_id, preview_item_idx) =
17069 workspace.active_pane().read_with(cx, |pane, _| {
17070 (pane.preview_item_id(), pane.preview_item_idx())
17071 });
17072
17073 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17074
17075 if let Some(preview_item_id) = preview_item_id {
17076 workspace.active_pane().update(cx, |pane, cx| {
17077 pane.remove_item(preview_item_id, false, false, window, cx);
17078 });
17079 }
17080 } else {
17081 workspace.add_item_to_active_pane(item, None, true, window, cx);
17082 }
17083 workspace.active_pane().update(cx, |pane, cx| {
17084 pane.set_preview_item_id(Some(item_id), cx);
17085 });
17086 }
17087
17088 pub fn rename(
17089 &mut self,
17090 _: &Rename,
17091 window: &mut Window,
17092 cx: &mut Context<Self>,
17093 ) -> Option<Task<Result<()>>> {
17094 use language::ToOffset as _;
17095
17096 let provider = self.semantics_provider.clone()?;
17097 let selection = self.selections.newest_anchor().clone();
17098 let (cursor_buffer, cursor_buffer_position) = self
17099 .buffer
17100 .read(cx)
17101 .text_anchor_for_position(selection.head(), cx)?;
17102 let (tail_buffer, cursor_buffer_position_end) = self
17103 .buffer
17104 .read(cx)
17105 .text_anchor_for_position(selection.tail(), cx)?;
17106 if tail_buffer != cursor_buffer {
17107 return None;
17108 }
17109
17110 let snapshot = cursor_buffer.read(cx).snapshot();
17111 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17112 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17113 let prepare_rename = provider
17114 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17115 .unwrap_or_else(|| Task::ready(Ok(None)));
17116 drop(snapshot);
17117
17118 Some(cx.spawn_in(window, async move |this, cx| {
17119 let rename_range = if let Some(range) = prepare_rename.await? {
17120 Some(range)
17121 } else {
17122 this.update(cx, |this, cx| {
17123 let buffer = this.buffer.read(cx).snapshot(cx);
17124 let mut buffer_highlights = this
17125 .document_highlights_for_position(selection.head(), &buffer)
17126 .filter(|highlight| {
17127 highlight.start.excerpt_id == selection.head().excerpt_id
17128 && highlight.end.excerpt_id == selection.head().excerpt_id
17129 });
17130 buffer_highlights
17131 .next()
17132 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17133 })?
17134 };
17135 if let Some(rename_range) = rename_range {
17136 this.update_in(cx, |this, window, cx| {
17137 let snapshot = cursor_buffer.read(cx).snapshot();
17138 let rename_buffer_range = rename_range.to_offset(&snapshot);
17139 let cursor_offset_in_rename_range =
17140 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17141 let cursor_offset_in_rename_range_end =
17142 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17143
17144 this.take_rename(false, window, cx);
17145 let buffer = this.buffer.read(cx).read(cx);
17146 let cursor_offset = selection.head().to_offset(&buffer);
17147 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17148 let rename_end = rename_start + rename_buffer_range.len();
17149 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17150 let mut old_highlight_id = None;
17151 let old_name: Arc<str> = buffer
17152 .chunks(rename_start..rename_end, true)
17153 .map(|chunk| {
17154 if old_highlight_id.is_none() {
17155 old_highlight_id = chunk.syntax_highlight_id;
17156 }
17157 chunk.text
17158 })
17159 .collect::<String>()
17160 .into();
17161
17162 drop(buffer);
17163
17164 // Position the selection in the rename editor so that it matches the current selection.
17165 this.show_local_selections = false;
17166 let rename_editor = cx.new(|cx| {
17167 let mut editor = Editor::single_line(window, cx);
17168 editor.buffer.update(cx, |buffer, cx| {
17169 buffer.edit([(0..0, old_name.clone())], None, cx)
17170 });
17171 let rename_selection_range = match cursor_offset_in_rename_range
17172 .cmp(&cursor_offset_in_rename_range_end)
17173 {
17174 Ordering::Equal => {
17175 editor.select_all(&SelectAll, window, cx);
17176 return editor;
17177 }
17178 Ordering::Less => {
17179 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17180 }
17181 Ordering::Greater => {
17182 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17183 }
17184 };
17185 if rename_selection_range.end > old_name.len() {
17186 editor.select_all(&SelectAll, window, cx);
17187 } else {
17188 editor.change_selections(Default::default(), window, cx, |s| {
17189 s.select_ranges([rename_selection_range]);
17190 });
17191 }
17192 editor
17193 });
17194 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17195 if e == &EditorEvent::Focused {
17196 cx.emit(EditorEvent::FocusedIn)
17197 }
17198 })
17199 .detach();
17200
17201 let write_highlights =
17202 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17203 let read_highlights =
17204 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17205 let ranges = write_highlights
17206 .iter()
17207 .flat_map(|(_, ranges)| ranges.iter())
17208 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17209 .cloned()
17210 .collect();
17211
17212 this.highlight_text::<Rename>(
17213 ranges,
17214 HighlightStyle {
17215 fade_out: Some(0.6),
17216 ..Default::default()
17217 },
17218 cx,
17219 );
17220 let rename_focus_handle = rename_editor.focus_handle(cx);
17221 window.focus(&rename_focus_handle);
17222 let block_id = this.insert_blocks(
17223 [BlockProperties {
17224 style: BlockStyle::Flex,
17225 placement: BlockPlacement::Below(range.start),
17226 height: Some(1),
17227 render: Arc::new({
17228 let rename_editor = rename_editor.clone();
17229 move |cx: &mut BlockContext| {
17230 let mut text_style = cx.editor_style.text.clone();
17231 if let Some(highlight_style) = old_highlight_id
17232 .and_then(|h| h.style(&cx.editor_style.syntax))
17233 {
17234 text_style = text_style.highlight(highlight_style);
17235 }
17236 div()
17237 .block_mouse_except_scroll()
17238 .pl(cx.anchor_x)
17239 .child(EditorElement::new(
17240 &rename_editor,
17241 EditorStyle {
17242 background: cx.theme().system().transparent,
17243 local_player: cx.editor_style.local_player,
17244 text: text_style,
17245 scrollbar_width: cx.editor_style.scrollbar_width,
17246 syntax: cx.editor_style.syntax.clone(),
17247 status: cx.editor_style.status.clone(),
17248 inlay_hints_style: HighlightStyle {
17249 font_weight: Some(FontWeight::BOLD),
17250 ..make_inlay_hints_style(cx.app)
17251 },
17252 edit_prediction_styles: make_suggestion_styles(
17253 cx.app,
17254 ),
17255 ..EditorStyle::default()
17256 },
17257 ))
17258 .into_any_element()
17259 }
17260 }),
17261 priority: 0,
17262 }],
17263 Some(Autoscroll::fit()),
17264 cx,
17265 )[0];
17266 this.pending_rename = Some(RenameState {
17267 range,
17268 old_name,
17269 editor: rename_editor,
17270 block_id,
17271 });
17272 })?;
17273 }
17274
17275 Ok(())
17276 }))
17277 }
17278
17279 pub fn confirm_rename(
17280 &mut self,
17281 _: &ConfirmRename,
17282 window: &mut Window,
17283 cx: &mut Context<Self>,
17284 ) -> Option<Task<Result<()>>> {
17285 let rename = self.take_rename(false, window, cx)?;
17286 let workspace = self.workspace()?.downgrade();
17287 let (buffer, start) = self
17288 .buffer
17289 .read(cx)
17290 .text_anchor_for_position(rename.range.start, cx)?;
17291 let (end_buffer, _) = self
17292 .buffer
17293 .read(cx)
17294 .text_anchor_for_position(rename.range.end, cx)?;
17295 if buffer != end_buffer {
17296 return None;
17297 }
17298
17299 let old_name = rename.old_name;
17300 let new_name = rename.editor.read(cx).text(cx);
17301
17302 let rename = self.semantics_provider.as_ref()?.perform_rename(
17303 &buffer,
17304 start,
17305 new_name.clone(),
17306 cx,
17307 )?;
17308
17309 Some(cx.spawn_in(window, async move |editor, cx| {
17310 let project_transaction = rename.await?;
17311 Self::open_project_transaction(
17312 &editor,
17313 workspace,
17314 project_transaction,
17315 format!("Rename: {} → {}", old_name, new_name),
17316 cx,
17317 )
17318 .await?;
17319
17320 editor.update(cx, |editor, cx| {
17321 editor.refresh_document_highlights(cx);
17322 })?;
17323 Ok(())
17324 }))
17325 }
17326
17327 fn take_rename(
17328 &mut self,
17329 moving_cursor: bool,
17330 window: &mut Window,
17331 cx: &mut Context<Self>,
17332 ) -> Option<RenameState> {
17333 let rename = self.pending_rename.take()?;
17334 if rename.editor.focus_handle(cx).is_focused(window) {
17335 window.focus(&self.focus_handle);
17336 }
17337
17338 self.remove_blocks(
17339 [rename.block_id].into_iter().collect(),
17340 Some(Autoscroll::fit()),
17341 cx,
17342 );
17343 self.clear_highlights::<Rename>(cx);
17344 self.show_local_selections = true;
17345
17346 if moving_cursor {
17347 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17348 editor
17349 .selections
17350 .newest::<usize>(&editor.display_snapshot(cx))
17351 .head()
17352 });
17353
17354 // Update the selection to match the position of the selection inside
17355 // the rename editor.
17356 let snapshot = self.buffer.read(cx).read(cx);
17357 let rename_range = rename.range.to_offset(&snapshot);
17358 let cursor_in_editor = snapshot
17359 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17360 .min(rename_range.end);
17361 drop(snapshot);
17362
17363 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17364 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17365 });
17366 } else {
17367 self.refresh_document_highlights(cx);
17368 }
17369
17370 Some(rename)
17371 }
17372
17373 pub fn pending_rename(&self) -> Option<&RenameState> {
17374 self.pending_rename.as_ref()
17375 }
17376
17377 fn format(
17378 &mut self,
17379 _: &Format,
17380 window: &mut Window,
17381 cx: &mut Context<Self>,
17382 ) -> Option<Task<Result<()>>> {
17383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17384
17385 let project = match &self.project {
17386 Some(project) => project.clone(),
17387 None => return None,
17388 };
17389
17390 Some(self.perform_format(
17391 project,
17392 FormatTrigger::Manual,
17393 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17394 window,
17395 cx,
17396 ))
17397 }
17398
17399 fn format_selections(
17400 &mut self,
17401 _: &FormatSelections,
17402 window: &mut Window,
17403 cx: &mut Context<Self>,
17404 ) -> Option<Task<Result<()>>> {
17405 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17406
17407 let project = match &self.project {
17408 Some(project) => project.clone(),
17409 None => return None,
17410 };
17411
17412 let ranges = self
17413 .selections
17414 .all_adjusted(&self.display_snapshot(cx))
17415 .into_iter()
17416 .map(|selection| selection.range())
17417 .collect_vec();
17418
17419 Some(self.perform_format(
17420 project,
17421 FormatTrigger::Manual,
17422 FormatTarget::Ranges(ranges),
17423 window,
17424 cx,
17425 ))
17426 }
17427
17428 fn perform_format(
17429 &mut self,
17430 project: Entity<Project>,
17431 trigger: FormatTrigger,
17432 target: FormatTarget,
17433 window: &mut Window,
17434 cx: &mut Context<Self>,
17435 ) -> Task<Result<()>> {
17436 let buffer = self.buffer.clone();
17437 let (buffers, target) = match target {
17438 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17439 FormatTarget::Ranges(selection_ranges) => {
17440 let multi_buffer = buffer.read(cx);
17441 let snapshot = multi_buffer.read(cx);
17442 let mut buffers = HashSet::default();
17443 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17444 BTreeMap::new();
17445 for selection_range in selection_ranges {
17446 for (buffer, buffer_range, _) in
17447 snapshot.range_to_buffer_ranges(selection_range)
17448 {
17449 let buffer_id = buffer.remote_id();
17450 let start = buffer.anchor_before(buffer_range.start);
17451 let end = buffer.anchor_after(buffer_range.end);
17452 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17453 buffer_id_to_ranges
17454 .entry(buffer_id)
17455 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17456 .or_insert_with(|| vec![start..end]);
17457 }
17458 }
17459 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17460 }
17461 };
17462
17463 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17464 let selections_prev = transaction_id_prev
17465 .and_then(|transaction_id_prev| {
17466 // default to selections as they were after the last edit, if we have them,
17467 // instead of how they are now.
17468 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17469 // will take you back to where you made the last edit, instead of staying where you scrolled
17470 self.selection_history
17471 .transaction(transaction_id_prev)
17472 .map(|t| t.0.clone())
17473 })
17474 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17475
17476 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17477 let format = project.update(cx, |project, cx| {
17478 project.format(buffers, target, true, trigger, cx)
17479 });
17480
17481 cx.spawn_in(window, async move |editor, cx| {
17482 let transaction = futures::select_biased! {
17483 transaction = format.log_err().fuse() => transaction,
17484 () = timeout => {
17485 log::warn!("timed out waiting for formatting");
17486 None
17487 }
17488 };
17489
17490 buffer
17491 .update(cx, |buffer, cx| {
17492 if let Some(transaction) = transaction
17493 && !buffer.is_singleton()
17494 {
17495 buffer.push_transaction(&transaction.0, cx);
17496 }
17497 cx.notify();
17498 })
17499 .ok();
17500
17501 if let Some(transaction_id_now) =
17502 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17503 {
17504 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17505 if has_new_transaction {
17506 _ = editor.update(cx, |editor, _| {
17507 editor
17508 .selection_history
17509 .insert_transaction(transaction_id_now, selections_prev);
17510 });
17511 }
17512 }
17513
17514 Ok(())
17515 })
17516 }
17517
17518 fn organize_imports(
17519 &mut self,
17520 _: &OrganizeImports,
17521 window: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) -> Option<Task<Result<()>>> {
17524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17525 let project = match &self.project {
17526 Some(project) => project.clone(),
17527 None => return None,
17528 };
17529 Some(self.perform_code_action_kind(
17530 project,
17531 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17532 window,
17533 cx,
17534 ))
17535 }
17536
17537 fn perform_code_action_kind(
17538 &mut self,
17539 project: Entity<Project>,
17540 kind: CodeActionKind,
17541 window: &mut Window,
17542 cx: &mut Context<Self>,
17543 ) -> Task<Result<()>> {
17544 let buffer = self.buffer.clone();
17545 let buffers = buffer.read(cx).all_buffers();
17546 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17547 let apply_action = project.update(cx, |project, cx| {
17548 project.apply_code_action_kind(buffers, kind, true, cx)
17549 });
17550 cx.spawn_in(window, async move |_, cx| {
17551 let transaction = futures::select_biased! {
17552 () = timeout => {
17553 log::warn!("timed out waiting for executing code action");
17554 None
17555 }
17556 transaction = apply_action.log_err().fuse() => transaction,
17557 };
17558 buffer
17559 .update(cx, |buffer, cx| {
17560 // check if we need this
17561 if let Some(transaction) = transaction
17562 && !buffer.is_singleton()
17563 {
17564 buffer.push_transaction(&transaction.0, cx);
17565 }
17566 cx.notify();
17567 })
17568 .ok();
17569 Ok(())
17570 })
17571 }
17572
17573 pub fn restart_language_server(
17574 &mut self,
17575 _: &RestartLanguageServer,
17576 _: &mut Window,
17577 cx: &mut Context<Self>,
17578 ) {
17579 if let Some(project) = self.project.clone() {
17580 self.buffer.update(cx, |multi_buffer, cx| {
17581 project.update(cx, |project, cx| {
17582 project.restart_language_servers_for_buffers(
17583 multi_buffer.all_buffers().into_iter().collect(),
17584 HashSet::default(),
17585 cx,
17586 );
17587 });
17588 })
17589 }
17590 }
17591
17592 pub fn stop_language_server(
17593 &mut self,
17594 _: &StopLanguageServer,
17595 _: &mut Window,
17596 cx: &mut Context<Self>,
17597 ) {
17598 if let Some(project) = self.project.clone() {
17599 self.buffer.update(cx, |multi_buffer, cx| {
17600 project.update(cx, |project, cx| {
17601 project.stop_language_servers_for_buffers(
17602 multi_buffer.all_buffers().into_iter().collect(),
17603 HashSet::default(),
17604 cx,
17605 );
17606 });
17607 });
17608 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17609 }
17610 }
17611
17612 fn cancel_language_server_work(
17613 workspace: &mut Workspace,
17614 _: &actions::CancelLanguageServerWork,
17615 _: &mut Window,
17616 cx: &mut Context<Workspace>,
17617 ) {
17618 let project = workspace.project();
17619 let buffers = workspace
17620 .active_item(cx)
17621 .and_then(|item| item.act_as::<Editor>(cx))
17622 .map_or(HashSet::default(), |editor| {
17623 editor.read(cx).buffer.read(cx).all_buffers()
17624 });
17625 project.update(cx, |project, cx| {
17626 project.cancel_language_server_work_for_buffers(buffers, cx);
17627 });
17628 }
17629
17630 fn show_character_palette(
17631 &mut self,
17632 _: &ShowCharacterPalette,
17633 window: &mut Window,
17634 _: &mut Context<Self>,
17635 ) {
17636 window.show_character_palette();
17637 }
17638
17639 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17640 if !self.diagnostics_enabled() {
17641 return;
17642 }
17643
17644 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17645 let buffer = self.buffer.read(cx).snapshot(cx);
17646 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17647 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17648 let is_valid = buffer
17649 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17650 .any(|entry| {
17651 entry.diagnostic.is_primary
17652 && !entry.range.is_empty()
17653 && entry.range.start == primary_range_start
17654 && entry.diagnostic.message == active_diagnostics.active_message
17655 });
17656
17657 if !is_valid {
17658 self.dismiss_diagnostics(cx);
17659 }
17660 }
17661 }
17662
17663 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17664 match &self.active_diagnostics {
17665 ActiveDiagnostic::Group(group) => Some(group),
17666 _ => None,
17667 }
17668 }
17669
17670 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17671 if !self.diagnostics_enabled() {
17672 return;
17673 }
17674 self.dismiss_diagnostics(cx);
17675 self.active_diagnostics = ActiveDiagnostic::All;
17676 }
17677
17678 fn activate_diagnostics(
17679 &mut self,
17680 buffer_id: BufferId,
17681 diagnostic: DiagnosticEntryRef<'_, usize>,
17682 window: &mut Window,
17683 cx: &mut Context<Self>,
17684 ) {
17685 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17686 return;
17687 }
17688 self.dismiss_diagnostics(cx);
17689 let snapshot = self.snapshot(window, cx);
17690 let buffer = self.buffer.read(cx).snapshot(cx);
17691 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17692 return;
17693 };
17694
17695 let diagnostic_group = buffer
17696 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17697 .collect::<Vec<_>>();
17698
17699 let blocks =
17700 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17701
17702 let blocks = self.display_map.update(cx, |display_map, cx| {
17703 display_map.insert_blocks(blocks, cx).into_iter().collect()
17704 });
17705 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17706 active_range: buffer.anchor_before(diagnostic.range.start)
17707 ..buffer.anchor_after(diagnostic.range.end),
17708 active_message: diagnostic.diagnostic.message.clone(),
17709 group_id: diagnostic.diagnostic.group_id,
17710 blocks,
17711 });
17712 cx.notify();
17713 }
17714
17715 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17716 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17717 return;
17718 };
17719
17720 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17721 if let ActiveDiagnostic::Group(group) = prev {
17722 self.display_map.update(cx, |display_map, cx| {
17723 display_map.remove_blocks(group.blocks, cx);
17724 });
17725 cx.notify();
17726 }
17727 }
17728
17729 /// Disable inline diagnostics rendering for this editor.
17730 pub fn disable_inline_diagnostics(&mut self) {
17731 self.inline_diagnostics_enabled = false;
17732 self.inline_diagnostics_update = Task::ready(());
17733 self.inline_diagnostics.clear();
17734 }
17735
17736 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17737 self.diagnostics_enabled = false;
17738 self.dismiss_diagnostics(cx);
17739 self.inline_diagnostics_update = Task::ready(());
17740 self.inline_diagnostics.clear();
17741 }
17742
17743 pub fn disable_word_completions(&mut self) {
17744 self.word_completions_enabled = false;
17745 }
17746
17747 pub fn diagnostics_enabled(&self) -> bool {
17748 self.diagnostics_enabled && self.mode.is_full()
17749 }
17750
17751 pub fn inline_diagnostics_enabled(&self) -> bool {
17752 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17753 }
17754
17755 pub fn show_inline_diagnostics(&self) -> bool {
17756 self.show_inline_diagnostics
17757 }
17758
17759 pub fn toggle_inline_diagnostics(
17760 &mut self,
17761 _: &ToggleInlineDiagnostics,
17762 window: &mut Window,
17763 cx: &mut Context<Editor>,
17764 ) {
17765 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17766 self.refresh_inline_diagnostics(false, window, cx);
17767 }
17768
17769 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17770 self.diagnostics_max_severity = severity;
17771 self.display_map.update(cx, |display_map, _| {
17772 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17773 });
17774 }
17775
17776 pub fn toggle_diagnostics(
17777 &mut self,
17778 _: &ToggleDiagnostics,
17779 window: &mut Window,
17780 cx: &mut Context<Editor>,
17781 ) {
17782 if !self.diagnostics_enabled() {
17783 return;
17784 }
17785
17786 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17787 EditorSettings::get_global(cx)
17788 .diagnostics_max_severity
17789 .filter(|severity| severity != &DiagnosticSeverity::Off)
17790 .unwrap_or(DiagnosticSeverity::Hint)
17791 } else {
17792 DiagnosticSeverity::Off
17793 };
17794 self.set_max_diagnostics_severity(new_severity, cx);
17795 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17796 self.active_diagnostics = ActiveDiagnostic::None;
17797 self.inline_diagnostics_update = Task::ready(());
17798 self.inline_diagnostics.clear();
17799 } else {
17800 self.refresh_inline_diagnostics(false, window, cx);
17801 }
17802
17803 cx.notify();
17804 }
17805
17806 pub fn toggle_minimap(
17807 &mut self,
17808 _: &ToggleMinimap,
17809 window: &mut Window,
17810 cx: &mut Context<Editor>,
17811 ) {
17812 if self.supports_minimap(cx) {
17813 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17814 }
17815 }
17816
17817 fn refresh_inline_diagnostics(
17818 &mut self,
17819 debounce: bool,
17820 window: &mut Window,
17821 cx: &mut Context<Self>,
17822 ) {
17823 let max_severity = ProjectSettings::get_global(cx)
17824 .diagnostics
17825 .inline
17826 .max_severity
17827 .unwrap_or(self.diagnostics_max_severity);
17828
17829 if !self.inline_diagnostics_enabled()
17830 || !self.show_inline_diagnostics
17831 || max_severity == DiagnosticSeverity::Off
17832 {
17833 self.inline_diagnostics_update = Task::ready(());
17834 self.inline_diagnostics.clear();
17835 return;
17836 }
17837
17838 let debounce_ms = ProjectSettings::get_global(cx)
17839 .diagnostics
17840 .inline
17841 .update_debounce_ms;
17842 let debounce = if debounce && debounce_ms > 0 {
17843 Some(Duration::from_millis(debounce_ms))
17844 } else {
17845 None
17846 };
17847 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17848 if let Some(debounce) = debounce {
17849 cx.background_executor().timer(debounce).await;
17850 }
17851 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17852 editor
17853 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17854 .ok()
17855 }) else {
17856 return;
17857 };
17858
17859 let new_inline_diagnostics = cx
17860 .background_spawn(async move {
17861 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17862 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17863 let message = diagnostic_entry
17864 .diagnostic
17865 .message
17866 .split_once('\n')
17867 .map(|(line, _)| line)
17868 .map(SharedString::new)
17869 .unwrap_or_else(|| {
17870 SharedString::new(&*diagnostic_entry.diagnostic.message)
17871 });
17872 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17873 let (Ok(i) | Err(i)) = inline_diagnostics
17874 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17875 inline_diagnostics.insert(
17876 i,
17877 (
17878 start_anchor,
17879 InlineDiagnostic {
17880 message,
17881 group_id: diagnostic_entry.diagnostic.group_id,
17882 start: diagnostic_entry.range.start.to_point(&snapshot),
17883 is_primary: diagnostic_entry.diagnostic.is_primary,
17884 severity: diagnostic_entry.diagnostic.severity,
17885 },
17886 ),
17887 );
17888 }
17889 inline_diagnostics
17890 })
17891 .await;
17892
17893 editor
17894 .update(cx, |editor, cx| {
17895 editor.inline_diagnostics = new_inline_diagnostics;
17896 cx.notify();
17897 })
17898 .ok();
17899 });
17900 }
17901
17902 fn pull_diagnostics(
17903 &mut self,
17904 buffer_id: Option<BufferId>,
17905 window: &Window,
17906 cx: &mut Context<Self>,
17907 ) -> Option<()> {
17908 if self.ignore_lsp_data() {
17909 return None;
17910 }
17911 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17912 .diagnostics
17913 .lsp_pull_diagnostics;
17914 if !pull_diagnostics_settings.enabled {
17915 return None;
17916 }
17917 let project = self.project()?.downgrade();
17918 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17919 let mut buffers = self.buffer.read(cx).all_buffers();
17920 buffers.retain(|buffer| {
17921 let buffer_id_to_retain = buffer.read(cx).remote_id();
17922 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17923 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17924 });
17925 if buffers.is_empty() {
17926 self.pull_diagnostics_task = Task::ready(());
17927 return None;
17928 }
17929
17930 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17931 cx.background_executor().timer(debounce).await;
17932
17933 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17934 buffers
17935 .into_iter()
17936 .filter_map(|buffer| {
17937 project
17938 .update(cx, |project, cx| {
17939 project.lsp_store().update(cx, |lsp_store, cx| {
17940 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17941 })
17942 })
17943 .ok()
17944 })
17945 .collect::<FuturesUnordered<_>>()
17946 }) else {
17947 return;
17948 };
17949
17950 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17951 match pull_task {
17952 Ok(()) => {
17953 if editor
17954 .update_in(cx, |editor, window, cx| {
17955 editor.update_diagnostics_state(window, cx);
17956 })
17957 .is_err()
17958 {
17959 return;
17960 }
17961 }
17962 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17963 }
17964 }
17965 });
17966
17967 Some(())
17968 }
17969
17970 pub fn set_selections_from_remote(
17971 &mut self,
17972 selections: Vec<Selection<Anchor>>,
17973 pending_selection: Option<Selection<Anchor>>,
17974 window: &mut Window,
17975 cx: &mut Context<Self>,
17976 ) {
17977 let old_cursor_position = self.selections.newest_anchor().head();
17978 self.selections.change_with(cx, |s| {
17979 s.select_anchors(selections);
17980 if let Some(pending_selection) = pending_selection {
17981 s.set_pending(pending_selection, SelectMode::Character);
17982 } else {
17983 s.clear_pending();
17984 }
17985 });
17986 self.selections_did_change(
17987 false,
17988 &old_cursor_position,
17989 SelectionEffects::default(),
17990 window,
17991 cx,
17992 );
17993 }
17994
17995 pub fn transact(
17996 &mut self,
17997 window: &mut Window,
17998 cx: &mut Context<Self>,
17999 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18000 ) -> Option<TransactionId> {
18001 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18002 this.start_transaction_at(Instant::now(), window, cx);
18003 update(this, window, cx);
18004 this.end_transaction_at(Instant::now(), cx)
18005 })
18006 }
18007
18008 pub fn start_transaction_at(
18009 &mut self,
18010 now: Instant,
18011 window: &mut Window,
18012 cx: &mut Context<Self>,
18013 ) -> Option<TransactionId> {
18014 self.end_selection(window, cx);
18015 if let Some(tx_id) = self
18016 .buffer
18017 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18018 {
18019 self.selection_history
18020 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18021 cx.emit(EditorEvent::TransactionBegun {
18022 transaction_id: tx_id,
18023 });
18024 Some(tx_id)
18025 } else {
18026 None
18027 }
18028 }
18029
18030 pub fn end_transaction_at(
18031 &mut self,
18032 now: Instant,
18033 cx: &mut Context<Self>,
18034 ) -> Option<TransactionId> {
18035 if let Some(transaction_id) = self
18036 .buffer
18037 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18038 {
18039 if let Some((_, end_selections)) =
18040 self.selection_history.transaction_mut(transaction_id)
18041 {
18042 *end_selections = Some(self.selections.disjoint_anchors_arc());
18043 } else {
18044 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18045 }
18046
18047 cx.emit(EditorEvent::Edited { transaction_id });
18048 Some(transaction_id)
18049 } else {
18050 None
18051 }
18052 }
18053
18054 pub fn modify_transaction_selection_history(
18055 &mut self,
18056 transaction_id: TransactionId,
18057 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18058 ) -> bool {
18059 self.selection_history
18060 .transaction_mut(transaction_id)
18061 .map(modify)
18062 .is_some()
18063 }
18064
18065 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18066 if self.selection_mark_mode {
18067 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18068 s.move_with(|_, sel| {
18069 sel.collapse_to(sel.head(), SelectionGoal::None);
18070 });
18071 })
18072 }
18073 self.selection_mark_mode = true;
18074 cx.notify();
18075 }
18076
18077 pub fn swap_selection_ends(
18078 &mut self,
18079 _: &actions::SwapSelectionEnds,
18080 window: &mut Window,
18081 cx: &mut Context<Self>,
18082 ) {
18083 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18084 s.move_with(|_, sel| {
18085 if sel.start != sel.end {
18086 sel.reversed = !sel.reversed
18087 }
18088 });
18089 });
18090 self.request_autoscroll(Autoscroll::newest(), cx);
18091 cx.notify();
18092 }
18093
18094 pub fn toggle_focus(
18095 workspace: &mut Workspace,
18096 _: &actions::ToggleFocus,
18097 window: &mut Window,
18098 cx: &mut Context<Workspace>,
18099 ) {
18100 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18101 return;
18102 };
18103 workspace.activate_item(&item, true, true, window, cx);
18104 }
18105
18106 pub fn toggle_fold(
18107 &mut self,
18108 _: &actions::ToggleFold,
18109 window: &mut Window,
18110 cx: &mut Context<Self>,
18111 ) {
18112 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18113 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18114 let selection = self.selections.newest::<Point>(&display_map);
18115
18116 let range = if selection.is_empty() {
18117 let point = selection.head().to_display_point(&display_map);
18118 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18119 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18120 .to_point(&display_map);
18121 start..end
18122 } else {
18123 selection.range()
18124 };
18125 if display_map.folds_in_range(range).next().is_some() {
18126 self.unfold_lines(&Default::default(), window, cx)
18127 } else {
18128 self.fold(&Default::default(), window, cx)
18129 }
18130 } else {
18131 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18132 let buffer_ids: HashSet<_> = self
18133 .selections
18134 .disjoint_anchor_ranges()
18135 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18136 .collect();
18137
18138 let should_unfold = buffer_ids
18139 .iter()
18140 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18141
18142 for buffer_id in buffer_ids {
18143 if should_unfold {
18144 self.unfold_buffer(buffer_id, cx);
18145 } else {
18146 self.fold_buffer(buffer_id, cx);
18147 }
18148 }
18149 }
18150 }
18151
18152 pub fn toggle_fold_recursive(
18153 &mut self,
18154 _: &actions::ToggleFoldRecursive,
18155 window: &mut Window,
18156 cx: &mut Context<Self>,
18157 ) {
18158 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18159
18160 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18161 let range = if selection.is_empty() {
18162 let point = selection.head().to_display_point(&display_map);
18163 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18164 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18165 .to_point(&display_map);
18166 start..end
18167 } else {
18168 selection.range()
18169 };
18170 if display_map.folds_in_range(range).next().is_some() {
18171 self.unfold_recursive(&Default::default(), window, cx)
18172 } else {
18173 self.fold_recursive(&Default::default(), window, cx)
18174 }
18175 }
18176
18177 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18178 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18179 let mut to_fold = Vec::new();
18180 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18181 let selections = self.selections.all_adjusted(&display_map);
18182
18183 for selection in selections {
18184 let range = selection.range().sorted();
18185 let buffer_start_row = range.start.row;
18186
18187 if range.start.row != range.end.row {
18188 let mut found = false;
18189 let mut row = range.start.row;
18190 while row <= range.end.row {
18191 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18192 {
18193 found = true;
18194 row = crease.range().end.row + 1;
18195 to_fold.push(crease);
18196 } else {
18197 row += 1
18198 }
18199 }
18200 if found {
18201 continue;
18202 }
18203 }
18204
18205 for row in (0..=range.start.row).rev() {
18206 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18207 && crease.range().end.row >= buffer_start_row
18208 {
18209 to_fold.push(crease);
18210 if row <= range.start.row {
18211 break;
18212 }
18213 }
18214 }
18215 }
18216
18217 self.fold_creases(to_fold, true, window, cx);
18218 } else {
18219 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18220 let buffer_ids = self
18221 .selections
18222 .disjoint_anchor_ranges()
18223 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18224 .collect::<HashSet<_>>();
18225 for buffer_id in buffer_ids {
18226 self.fold_buffer(buffer_id, cx);
18227 }
18228 }
18229 }
18230
18231 pub fn toggle_fold_all(
18232 &mut self,
18233 _: &actions::ToggleFoldAll,
18234 window: &mut Window,
18235 cx: &mut Context<Self>,
18236 ) {
18237 if self.buffer.read(cx).is_singleton() {
18238 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18239 let has_folds = display_map
18240 .folds_in_range(0..display_map.buffer_snapshot().len())
18241 .next()
18242 .is_some();
18243
18244 if has_folds {
18245 self.unfold_all(&actions::UnfoldAll, window, cx);
18246 } else {
18247 self.fold_all(&actions::FoldAll, window, cx);
18248 }
18249 } else {
18250 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18251 let should_unfold = buffer_ids
18252 .iter()
18253 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18254
18255 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18256 editor
18257 .update_in(cx, |editor, _, cx| {
18258 for buffer_id in buffer_ids {
18259 if should_unfold {
18260 editor.unfold_buffer(buffer_id, cx);
18261 } else {
18262 editor.fold_buffer(buffer_id, cx);
18263 }
18264 }
18265 })
18266 .ok();
18267 });
18268 }
18269 }
18270
18271 fn fold_at_level(
18272 &mut self,
18273 fold_at: &FoldAtLevel,
18274 window: &mut Window,
18275 cx: &mut Context<Self>,
18276 ) {
18277 if !self.buffer.read(cx).is_singleton() {
18278 return;
18279 }
18280
18281 let fold_at_level = fold_at.0;
18282 let snapshot = self.buffer.read(cx).snapshot(cx);
18283 let mut to_fold = Vec::new();
18284 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18285
18286 let row_ranges_to_keep: Vec<Range<u32>> = self
18287 .selections
18288 .all::<Point>(&self.display_snapshot(cx))
18289 .into_iter()
18290 .map(|sel| sel.start.row..sel.end.row)
18291 .collect();
18292
18293 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18294 while start_row < end_row {
18295 match self
18296 .snapshot(window, cx)
18297 .crease_for_buffer_row(MultiBufferRow(start_row))
18298 {
18299 Some(crease) => {
18300 let nested_start_row = crease.range().start.row + 1;
18301 let nested_end_row = crease.range().end.row;
18302
18303 if current_level < fold_at_level {
18304 stack.push((nested_start_row, nested_end_row, current_level + 1));
18305 } else if current_level == fold_at_level {
18306 // Fold iff there is no selection completely contained within the fold region
18307 if !row_ranges_to_keep.iter().any(|selection| {
18308 selection.end >= nested_start_row
18309 && selection.start <= nested_end_row
18310 }) {
18311 to_fold.push(crease);
18312 }
18313 }
18314
18315 start_row = nested_end_row + 1;
18316 }
18317 None => start_row += 1,
18318 }
18319 }
18320 }
18321
18322 self.fold_creases(to_fold, true, window, cx);
18323 }
18324
18325 pub fn fold_at_level_1(
18326 &mut self,
18327 _: &actions::FoldAtLevel1,
18328 window: &mut Window,
18329 cx: &mut Context<Self>,
18330 ) {
18331 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18332 }
18333
18334 pub fn fold_at_level_2(
18335 &mut self,
18336 _: &actions::FoldAtLevel2,
18337 window: &mut Window,
18338 cx: &mut Context<Self>,
18339 ) {
18340 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18341 }
18342
18343 pub fn fold_at_level_3(
18344 &mut self,
18345 _: &actions::FoldAtLevel3,
18346 window: &mut Window,
18347 cx: &mut Context<Self>,
18348 ) {
18349 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18350 }
18351
18352 pub fn fold_at_level_4(
18353 &mut self,
18354 _: &actions::FoldAtLevel4,
18355 window: &mut Window,
18356 cx: &mut Context<Self>,
18357 ) {
18358 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18359 }
18360
18361 pub fn fold_at_level_5(
18362 &mut self,
18363 _: &actions::FoldAtLevel5,
18364 window: &mut Window,
18365 cx: &mut Context<Self>,
18366 ) {
18367 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18368 }
18369
18370 pub fn fold_at_level_6(
18371 &mut self,
18372 _: &actions::FoldAtLevel6,
18373 window: &mut Window,
18374 cx: &mut Context<Self>,
18375 ) {
18376 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18377 }
18378
18379 pub fn fold_at_level_7(
18380 &mut self,
18381 _: &actions::FoldAtLevel7,
18382 window: &mut Window,
18383 cx: &mut Context<Self>,
18384 ) {
18385 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18386 }
18387
18388 pub fn fold_at_level_8(
18389 &mut self,
18390 _: &actions::FoldAtLevel8,
18391 window: &mut Window,
18392 cx: &mut Context<Self>,
18393 ) {
18394 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18395 }
18396
18397 pub fn fold_at_level_9(
18398 &mut self,
18399 _: &actions::FoldAtLevel9,
18400 window: &mut Window,
18401 cx: &mut Context<Self>,
18402 ) {
18403 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18404 }
18405
18406 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18407 if self.buffer.read(cx).is_singleton() {
18408 let mut fold_ranges = Vec::new();
18409 let snapshot = self.buffer.read(cx).snapshot(cx);
18410
18411 for row in 0..snapshot.max_row().0 {
18412 if let Some(foldable_range) = self
18413 .snapshot(window, cx)
18414 .crease_for_buffer_row(MultiBufferRow(row))
18415 {
18416 fold_ranges.push(foldable_range);
18417 }
18418 }
18419
18420 self.fold_creases(fold_ranges, true, window, cx);
18421 } else {
18422 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18423 editor
18424 .update_in(cx, |editor, _, cx| {
18425 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18426 editor.fold_buffer(buffer_id, cx);
18427 }
18428 })
18429 .ok();
18430 });
18431 }
18432 }
18433
18434 pub fn fold_function_bodies(
18435 &mut self,
18436 _: &actions::FoldFunctionBodies,
18437 window: &mut Window,
18438 cx: &mut Context<Self>,
18439 ) {
18440 let snapshot = self.buffer.read(cx).snapshot(cx);
18441
18442 let ranges = snapshot
18443 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18444 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18445 .collect::<Vec<_>>();
18446
18447 let creases = ranges
18448 .into_iter()
18449 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18450 .collect();
18451
18452 self.fold_creases(creases, true, window, cx);
18453 }
18454
18455 pub fn fold_recursive(
18456 &mut self,
18457 _: &actions::FoldRecursive,
18458 window: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 let mut to_fold = Vec::new();
18462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18463 let selections = self.selections.all_adjusted(&display_map);
18464
18465 for selection in selections {
18466 let range = selection.range().sorted();
18467 let buffer_start_row = range.start.row;
18468
18469 if range.start.row != range.end.row {
18470 let mut found = false;
18471 for row in range.start.row..=range.end.row {
18472 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18473 found = true;
18474 to_fold.push(crease);
18475 }
18476 }
18477 if found {
18478 continue;
18479 }
18480 }
18481
18482 for row in (0..=range.start.row).rev() {
18483 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18484 if crease.range().end.row >= buffer_start_row {
18485 to_fold.push(crease);
18486 } else {
18487 break;
18488 }
18489 }
18490 }
18491 }
18492
18493 self.fold_creases(to_fold, true, window, cx);
18494 }
18495
18496 pub fn fold_at(
18497 &mut self,
18498 buffer_row: MultiBufferRow,
18499 window: &mut Window,
18500 cx: &mut Context<Self>,
18501 ) {
18502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18503
18504 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18505 let autoscroll = self
18506 .selections
18507 .all::<Point>(&display_map)
18508 .iter()
18509 .any(|selection| crease.range().overlaps(&selection.range()));
18510
18511 self.fold_creases(vec![crease], autoscroll, window, cx);
18512 }
18513 }
18514
18515 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18516 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18517 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18518 let buffer = display_map.buffer_snapshot();
18519 let selections = self.selections.all::<Point>(&display_map);
18520 let ranges = selections
18521 .iter()
18522 .map(|s| {
18523 let range = s.display_range(&display_map).sorted();
18524 let mut start = range.start.to_point(&display_map);
18525 let mut end = range.end.to_point(&display_map);
18526 start.column = 0;
18527 end.column = buffer.line_len(MultiBufferRow(end.row));
18528 start..end
18529 })
18530 .collect::<Vec<_>>();
18531
18532 self.unfold_ranges(&ranges, true, true, cx);
18533 } else {
18534 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18535 let buffer_ids = self
18536 .selections
18537 .disjoint_anchor_ranges()
18538 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18539 .collect::<HashSet<_>>();
18540 for buffer_id in buffer_ids {
18541 self.unfold_buffer(buffer_id, cx);
18542 }
18543 }
18544 }
18545
18546 pub fn unfold_recursive(
18547 &mut self,
18548 _: &UnfoldRecursive,
18549 _window: &mut Window,
18550 cx: &mut Context<Self>,
18551 ) {
18552 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18553 let selections = self.selections.all::<Point>(&display_map);
18554 let ranges = selections
18555 .iter()
18556 .map(|s| {
18557 let mut range = s.display_range(&display_map).sorted();
18558 *range.start.column_mut() = 0;
18559 *range.end.column_mut() = display_map.line_len(range.end.row());
18560 let start = range.start.to_point(&display_map);
18561 let end = range.end.to_point(&display_map);
18562 start..end
18563 })
18564 .collect::<Vec<_>>();
18565
18566 self.unfold_ranges(&ranges, true, true, cx);
18567 }
18568
18569 pub fn unfold_at(
18570 &mut self,
18571 buffer_row: MultiBufferRow,
18572 _window: &mut Window,
18573 cx: &mut Context<Self>,
18574 ) {
18575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18576
18577 let intersection_range = Point::new(buffer_row.0, 0)
18578 ..Point::new(
18579 buffer_row.0,
18580 display_map.buffer_snapshot().line_len(buffer_row),
18581 );
18582
18583 let autoscroll = self
18584 .selections
18585 .all::<Point>(&display_map)
18586 .iter()
18587 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18588
18589 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18590 }
18591
18592 pub fn unfold_all(
18593 &mut self,
18594 _: &actions::UnfoldAll,
18595 _window: &mut Window,
18596 cx: &mut Context<Self>,
18597 ) {
18598 if self.buffer.read(cx).is_singleton() {
18599 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18600 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18601 } else {
18602 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18603 editor
18604 .update(cx, |editor, cx| {
18605 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18606 editor.unfold_buffer(buffer_id, cx);
18607 }
18608 })
18609 .ok();
18610 });
18611 }
18612 }
18613
18614 pub fn fold_selected_ranges(
18615 &mut self,
18616 _: &FoldSelectedRanges,
18617 window: &mut Window,
18618 cx: &mut Context<Self>,
18619 ) {
18620 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18621 let selections = self.selections.all_adjusted(&display_map);
18622 let ranges = selections
18623 .into_iter()
18624 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18625 .collect::<Vec<_>>();
18626 self.fold_creases(ranges, true, window, cx);
18627 }
18628
18629 pub fn fold_ranges<T: ToOffset + Clone>(
18630 &mut self,
18631 ranges: Vec<Range<T>>,
18632 auto_scroll: bool,
18633 window: &mut Window,
18634 cx: &mut Context<Self>,
18635 ) {
18636 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18637 let ranges = ranges
18638 .into_iter()
18639 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18640 .collect::<Vec<_>>();
18641 self.fold_creases(ranges, auto_scroll, window, cx);
18642 }
18643
18644 pub fn fold_creases<T: ToOffset + Clone>(
18645 &mut self,
18646 creases: Vec<Crease<T>>,
18647 auto_scroll: bool,
18648 _window: &mut Window,
18649 cx: &mut Context<Self>,
18650 ) {
18651 if creases.is_empty() {
18652 return;
18653 }
18654
18655 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18656
18657 if auto_scroll {
18658 self.request_autoscroll(Autoscroll::fit(), cx);
18659 }
18660
18661 cx.notify();
18662
18663 self.scrollbar_marker_state.dirty = true;
18664 self.folds_did_change(cx);
18665 }
18666
18667 /// Removes any folds whose ranges intersect any of the given ranges.
18668 pub fn unfold_ranges<T: ToOffset + Clone>(
18669 &mut self,
18670 ranges: &[Range<T>],
18671 inclusive: bool,
18672 auto_scroll: bool,
18673 cx: &mut Context<Self>,
18674 ) {
18675 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18676 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18677 });
18678 self.folds_did_change(cx);
18679 }
18680
18681 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18682 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18683 return;
18684 }
18685 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18686 self.display_map.update(cx, |display_map, cx| {
18687 display_map.fold_buffers([buffer_id], cx)
18688 });
18689 cx.emit(EditorEvent::BufferFoldToggled {
18690 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18691 folded: true,
18692 });
18693 cx.notify();
18694 }
18695
18696 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18697 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18698 return;
18699 }
18700 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18701 self.display_map.update(cx, |display_map, cx| {
18702 display_map.unfold_buffers([buffer_id], cx);
18703 });
18704 cx.emit(EditorEvent::BufferFoldToggled {
18705 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18706 folded: false,
18707 });
18708 cx.notify();
18709 }
18710
18711 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18712 self.display_map.read(cx).is_buffer_folded(buffer)
18713 }
18714
18715 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18716 self.display_map.read(cx).folded_buffers()
18717 }
18718
18719 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18720 self.display_map.update(cx, |display_map, cx| {
18721 display_map.disable_header_for_buffer(buffer_id, cx);
18722 });
18723 cx.notify();
18724 }
18725
18726 /// Removes any folds with the given ranges.
18727 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18728 &mut self,
18729 ranges: &[Range<T>],
18730 type_id: TypeId,
18731 auto_scroll: bool,
18732 cx: &mut Context<Self>,
18733 ) {
18734 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18735 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18736 });
18737 self.folds_did_change(cx);
18738 }
18739
18740 fn remove_folds_with<T: ToOffset + Clone>(
18741 &mut self,
18742 ranges: &[Range<T>],
18743 auto_scroll: bool,
18744 cx: &mut Context<Self>,
18745 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18746 ) {
18747 if ranges.is_empty() {
18748 return;
18749 }
18750
18751 let mut buffers_affected = HashSet::default();
18752 let multi_buffer = self.buffer().read(cx);
18753 for range in ranges {
18754 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18755 buffers_affected.insert(buffer.read(cx).remote_id());
18756 };
18757 }
18758
18759 self.display_map.update(cx, update);
18760
18761 if auto_scroll {
18762 self.request_autoscroll(Autoscroll::fit(), cx);
18763 }
18764
18765 cx.notify();
18766 self.scrollbar_marker_state.dirty = true;
18767 self.active_indent_guides_state.dirty = true;
18768 }
18769
18770 pub fn update_renderer_widths(
18771 &mut self,
18772 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18773 cx: &mut Context<Self>,
18774 ) -> bool {
18775 self.display_map
18776 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18777 }
18778
18779 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18780 self.display_map.read(cx).fold_placeholder.clone()
18781 }
18782
18783 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18784 self.buffer.update(cx, |buffer, cx| {
18785 buffer.set_all_diff_hunks_expanded(cx);
18786 });
18787 }
18788
18789 pub fn expand_all_diff_hunks(
18790 &mut self,
18791 _: &ExpandAllDiffHunks,
18792 _window: &mut Window,
18793 cx: &mut Context<Self>,
18794 ) {
18795 self.buffer.update(cx, |buffer, cx| {
18796 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18797 });
18798 }
18799
18800 pub fn collapse_all_diff_hunks(
18801 &mut self,
18802 _: &CollapseAllDiffHunks,
18803 _window: &mut Window,
18804 cx: &mut Context<Self>,
18805 ) {
18806 self.buffer.update(cx, |buffer, cx| {
18807 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18808 });
18809 }
18810
18811 pub fn toggle_selected_diff_hunks(
18812 &mut self,
18813 _: &ToggleSelectedDiffHunks,
18814 _window: &mut Window,
18815 cx: &mut Context<Self>,
18816 ) {
18817 let ranges: Vec<_> = self
18818 .selections
18819 .disjoint_anchors()
18820 .iter()
18821 .map(|s| s.range())
18822 .collect();
18823 self.toggle_diff_hunks_in_ranges(ranges, cx);
18824 }
18825
18826 pub fn diff_hunks_in_ranges<'a>(
18827 &'a self,
18828 ranges: &'a [Range<Anchor>],
18829 buffer: &'a MultiBufferSnapshot,
18830 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18831 ranges.iter().flat_map(move |range| {
18832 let end_excerpt_id = range.end.excerpt_id;
18833 let range = range.to_point(buffer);
18834 let mut peek_end = range.end;
18835 if range.end.row < buffer.max_row().0 {
18836 peek_end = Point::new(range.end.row + 1, 0);
18837 }
18838 buffer
18839 .diff_hunks_in_range(range.start..peek_end)
18840 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18841 })
18842 }
18843
18844 pub fn has_stageable_diff_hunks_in_ranges(
18845 &self,
18846 ranges: &[Range<Anchor>],
18847 snapshot: &MultiBufferSnapshot,
18848 ) -> bool {
18849 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18850 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18851 }
18852
18853 pub fn toggle_staged_selected_diff_hunks(
18854 &mut self,
18855 _: &::git::ToggleStaged,
18856 _: &mut Window,
18857 cx: &mut Context<Self>,
18858 ) {
18859 let snapshot = self.buffer.read(cx).snapshot(cx);
18860 let ranges: Vec<_> = self
18861 .selections
18862 .disjoint_anchors()
18863 .iter()
18864 .map(|s| s.range())
18865 .collect();
18866 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18867 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18868 }
18869
18870 pub fn set_render_diff_hunk_controls(
18871 &mut self,
18872 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18873 cx: &mut Context<Self>,
18874 ) {
18875 self.render_diff_hunk_controls = render_diff_hunk_controls;
18876 cx.notify();
18877 }
18878
18879 pub fn stage_and_next(
18880 &mut self,
18881 _: &::git::StageAndNext,
18882 window: &mut Window,
18883 cx: &mut Context<Self>,
18884 ) {
18885 self.do_stage_or_unstage_and_next(true, window, cx);
18886 }
18887
18888 pub fn unstage_and_next(
18889 &mut self,
18890 _: &::git::UnstageAndNext,
18891 window: &mut Window,
18892 cx: &mut Context<Self>,
18893 ) {
18894 self.do_stage_or_unstage_and_next(false, window, cx);
18895 }
18896
18897 pub fn stage_or_unstage_diff_hunks(
18898 &mut self,
18899 stage: bool,
18900 ranges: Vec<Range<Anchor>>,
18901 cx: &mut Context<Self>,
18902 ) {
18903 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18904 cx.spawn(async move |this, cx| {
18905 task.await?;
18906 this.update(cx, |this, cx| {
18907 let snapshot = this.buffer.read(cx).snapshot(cx);
18908 let chunk_by = this
18909 .diff_hunks_in_ranges(&ranges, &snapshot)
18910 .chunk_by(|hunk| hunk.buffer_id);
18911 for (buffer_id, hunks) in &chunk_by {
18912 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18913 }
18914 })
18915 })
18916 .detach_and_log_err(cx);
18917 }
18918
18919 fn save_buffers_for_ranges_if_needed(
18920 &mut self,
18921 ranges: &[Range<Anchor>],
18922 cx: &mut Context<Editor>,
18923 ) -> Task<Result<()>> {
18924 let multibuffer = self.buffer.read(cx);
18925 let snapshot = multibuffer.read(cx);
18926 let buffer_ids: HashSet<_> = ranges
18927 .iter()
18928 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18929 .collect();
18930 drop(snapshot);
18931
18932 let mut buffers = HashSet::default();
18933 for buffer_id in buffer_ids {
18934 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18935 let buffer = buffer_entity.read(cx);
18936 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18937 {
18938 buffers.insert(buffer_entity);
18939 }
18940 }
18941 }
18942
18943 if let Some(project) = &self.project {
18944 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18945 } else {
18946 Task::ready(Ok(()))
18947 }
18948 }
18949
18950 fn do_stage_or_unstage_and_next(
18951 &mut self,
18952 stage: bool,
18953 window: &mut Window,
18954 cx: &mut Context<Self>,
18955 ) {
18956 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18957
18958 if ranges.iter().any(|range| range.start != range.end) {
18959 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18960 return;
18961 }
18962
18963 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18964 let snapshot = self.snapshot(window, cx);
18965 let position = self
18966 .selections
18967 .newest::<Point>(&snapshot.display_snapshot)
18968 .head();
18969 let mut row = snapshot
18970 .buffer_snapshot()
18971 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18972 .find(|hunk| hunk.row_range.start.0 > position.row)
18973 .map(|hunk| hunk.row_range.start);
18974
18975 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18976 // Outside of the project diff editor, wrap around to the beginning.
18977 if !all_diff_hunks_expanded {
18978 row = row.or_else(|| {
18979 snapshot
18980 .buffer_snapshot()
18981 .diff_hunks_in_range(Point::zero()..position)
18982 .find(|hunk| hunk.row_range.end.0 < position.row)
18983 .map(|hunk| hunk.row_range.start)
18984 });
18985 }
18986
18987 if let Some(row) = row {
18988 let destination = Point::new(row.0, 0);
18989 let autoscroll = Autoscroll::center();
18990
18991 self.unfold_ranges(&[destination..destination], false, false, cx);
18992 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18993 s.select_ranges([destination..destination]);
18994 });
18995 }
18996 }
18997
18998 fn do_stage_or_unstage(
18999 &self,
19000 stage: bool,
19001 buffer_id: BufferId,
19002 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19003 cx: &mut App,
19004 ) -> Option<()> {
19005 let project = self.project()?;
19006 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19007 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19008 let buffer_snapshot = buffer.read(cx).snapshot();
19009 let file_exists = buffer_snapshot
19010 .file()
19011 .is_some_and(|file| file.disk_state().exists());
19012 diff.update(cx, |diff, cx| {
19013 diff.stage_or_unstage_hunks(
19014 stage,
19015 &hunks
19016 .map(|hunk| buffer_diff::DiffHunk {
19017 buffer_range: hunk.buffer_range,
19018 diff_base_byte_range: hunk.diff_base_byte_range,
19019 secondary_status: hunk.secondary_status,
19020 range: Point::zero()..Point::zero(), // unused
19021 })
19022 .collect::<Vec<_>>(),
19023 &buffer_snapshot,
19024 file_exists,
19025 cx,
19026 )
19027 });
19028 None
19029 }
19030
19031 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19032 let ranges: Vec<_> = self
19033 .selections
19034 .disjoint_anchors()
19035 .iter()
19036 .map(|s| s.range())
19037 .collect();
19038 self.buffer
19039 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19040 }
19041
19042 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19043 self.buffer.update(cx, |buffer, cx| {
19044 let ranges = vec![Anchor::min()..Anchor::max()];
19045 if !buffer.all_diff_hunks_expanded()
19046 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19047 {
19048 buffer.collapse_diff_hunks(ranges, cx);
19049 true
19050 } else {
19051 false
19052 }
19053 })
19054 }
19055
19056 fn toggle_diff_hunks_in_ranges(
19057 &mut self,
19058 ranges: Vec<Range<Anchor>>,
19059 cx: &mut Context<Editor>,
19060 ) {
19061 self.buffer.update(cx, |buffer, cx| {
19062 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19063 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19064 })
19065 }
19066
19067 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19068 self.buffer.update(cx, |buffer, cx| {
19069 let snapshot = buffer.snapshot(cx);
19070 let excerpt_id = range.end.excerpt_id;
19071 let point_range = range.to_point(&snapshot);
19072 let expand = !buffer.single_hunk_is_expanded(range, cx);
19073 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19074 })
19075 }
19076
19077 pub(crate) fn apply_all_diff_hunks(
19078 &mut self,
19079 _: &ApplyAllDiffHunks,
19080 window: &mut Window,
19081 cx: &mut Context<Self>,
19082 ) {
19083 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19084
19085 let buffers = self.buffer.read(cx).all_buffers();
19086 for branch_buffer in buffers {
19087 branch_buffer.update(cx, |branch_buffer, cx| {
19088 branch_buffer.merge_into_base(Vec::new(), cx);
19089 });
19090 }
19091
19092 if let Some(project) = self.project.clone() {
19093 self.save(
19094 SaveOptions {
19095 format: true,
19096 autosave: false,
19097 },
19098 project,
19099 window,
19100 cx,
19101 )
19102 .detach_and_log_err(cx);
19103 }
19104 }
19105
19106 pub(crate) fn apply_selected_diff_hunks(
19107 &mut self,
19108 _: &ApplyDiffHunk,
19109 window: &mut Window,
19110 cx: &mut Context<Self>,
19111 ) {
19112 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19113 let snapshot = self.snapshot(window, cx);
19114 let hunks = snapshot.hunks_for_ranges(
19115 self.selections
19116 .all(&snapshot.display_snapshot)
19117 .into_iter()
19118 .map(|selection| selection.range()),
19119 );
19120 let mut ranges_by_buffer = HashMap::default();
19121 self.transact(window, cx, |editor, _window, cx| {
19122 for hunk in hunks {
19123 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19124 ranges_by_buffer
19125 .entry(buffer.clone())
19126 .or_insert_with(Vec::new)
19127 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19128 }
19129 }
19130
19131 for (buffer, ranges) in ranges_by_buffer {
19132 buffer.update(cx, |buffer, cx| {
19133 buffer.merge_into_base(ranges, cx);
19134 });
19135 }
19136 });
19137
19138 if let Some(project) = self.project.clone() {
19139 self.save(
19140 SaveOptions {
19141 format: true,
19142 autosave: false,
19143 },
19144 project,
19145 window,
19146 cx,
19147 )
19148 .detach_and_log_err(cx);
19149 }
19150 }
19151
19152 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19153 if hovered != self.gutter_hovered {
19154 self.gutter_hovered = hovered;
19155 cx.notify();
19156 }
19157 }
19158
19159 pub fn insert_blocks(
19160 &mut self,
19161 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19162 autoscroll: Option<Autoscroll>,
19163 cx: &mut Context<Self>,
19164 ) -> Vec<CustomBlockId> {
19165 let blocks = self
19166 .display_map
19167 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19168 if let Some(autoscroll) = autoscroll {
19169 self.request_autoscroll(autoscroll, cx);
19170 }
19171 cx.notify();
19172 blocks
19173 }
19174
19175 pub fn resize_blocks(
19176 &mut self,
19177 heights: HashMap<CustomBlockId, u32>,
19178 autoscroll: Option<Autoscroll>,
19179 cx: &mut Context<Self>,
19180 ) {
19181 self.display_map
19182 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19183 if let Some(autoscroll) = autoscroll {
19184 self.request_autoscroll(autoscroll, cx);
19185 }
19186 cx.notify();
19187 }
19188
19189 pub fn replace_blocks(
19190 &mut self,
19191 renderers: HashMap<CustomBlockId, RenderBlock>,
19192 autoscroll: Option<Autoscroll>,
19193 cx: &mut Context<Self>,
19194 ) {
19195 self.display_map
19196 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19197 if let Some(autoscroll) = autoscroll {
19198 self.request_autoscroll(autoscroll, cx);
19199 }
19200 cx.notify();
19201 }
19202
19203 pub fn remove_blocks(
19204 &mut self,
19205 block_ids: HashSet<CustomBlockId>,
19206 autoscroll: Option<Autoscroll>,
19207 cx: &mut Context<Self>,
19208 ) {
19209 self.display_map.update(cx, |display_map, cx| {
19210 display_map.remove_blocks(block_ids, cx)
19211 });
19212 if let Some(autoscroll) = autoscroll {
19213 self.request_autoscroll(autoscroll, cx);
19214 }
19215 cx.notify();
19216 }
19217
19218 pub fn row_for_block(
19219 &self,
19220 block_id: CustomBlockId,
19221 cx: &mut Context<Self>,
19222 ) -> Option<DisplayRow> {
19223 self.display_map
19224 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19225 }
19226
19227 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19228 self.focused_block = Some(focused_block);
19229 }
19230
19231 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19232 self.focused_block.take()
19233 }
19234
19235 pub fn insert_creases(
19236 &mut self,
19237 creases: impl IntoIterator<Item = Crease<Anchor>>,
19238 cx: &mut Context<Self>,
19239 ) -> Vec<CreaseId> {
19240 self.display_map
19241 .update(cx, |map, cx| map.insert_creases(creases, cx))
19242 }
19243
19244 pub fn remove_creases(
19245 &mut self,
19246 ids: impl IntoIterator<Item = CreaseId>,
19247 cx: &mut Context<Self>,
19248 ) -> Vec<(CreaseId, Range<Anchor>)> {
19249 self.display_map
19250 .update(cx, |map, cx| map.remove_creases(ids, cx))
19251 }
19252
19253 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19254 self.display_map
19255 .update(cx, |map, cx| map.snapshot(cx))
19256 .longest_row()
19257 }
19258
19259 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19260 self.display_map
19261 .update(cx, |map, cx| map.snapshot(cx))
19262 .max_point()
19263 }
19264
19265 pub fn text(&self, cx: &App) -> String {
19266 self.buffer.read(cx).read(cx).text()
19267 }
19268
19269 pub fn is_empty(&self, cx: &App) -> bool {
19270 self.buffer.read(cx).read(cx).is_empty()
19271 }
19272
19273 pub fn text_option(&self, cx: &App) -> Option<String> {
19274 let text = self.text(cx);
19275 let text = text.trim();
19276
19277 if text.is_empty() {
19278 return None;
19279 }
19280
19281 Some(text.to_string())
19282 }
19283
19284 pub fn set_text(
19285 &mut self,
19286 text: impl Into<Arc<str>>,
19287 window: &mut Window,
19288 cx: &mut Context<Self>,
19289 ) {
19290 self.transact(window, cx, |this, _, cx| {
19291 this.buffer
19292 .read(cx)
19293 .as_singleton()
19294 .expect("you can only call set_text on editors for singleton buffers")
19295 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19296 });
19297 }
19298
19299 pub fn display_text(&self, cx: &mut App) -> String {
19300 self.display_map
19301 .update(cx, |map, cx| map.snapshot(cx))
19302 .text()
19303 }
19304
19305 fn create_minimap(
19306 &self,
19307 minimap_settings: MinimapSettings,
19308 window: &mut Window,
19309 cx: &mut Context<Self>,
19310 ) -> Option<Entity<Self>> {
19311 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19312 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19313 }
19314
19315 fn initialize_new_minimap(
19316 &self,
19317 minimap_settings: MinimapSettings,
19318 window: &mut Window,
19319 cx: &mut Context<Self>,
19320 ) -> Entity<Self> {
19321 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19322
19323 let mut minimap = Editor::new_internal(
19324 EditorMode::Minimap {
19325 parent: cx.weak_entity(),
19326 },
19327 self.buffer.clone(),
19328 None,
19329 Some(self.display_map.clone()),
19330 window,
19331 cx,
19332 );
19333 minimap.scroll_manager.clone_state(&self.scroll_manager);
19334 minimap.set_text_style_refinement(TextStyleRefinement {
19335 font_size: Some(MINIMAP_FONT_SIZE),
19336 font_weight: Some(MINIMAP_FONT_WEIGHT),
19337 ..Default::default()
19338 });
19339 minimap.update_minimap_configuration(minimap_settings, cx);
19340 cx.new(|_| minimap)
19341 }
19342
19343 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19344 let current_line_highlight = minimap_settings
19345 .current_line_highlight
19346 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19347 self.set_current_line_highlight(Some(current_line_highlight));
19348 }
19349
19350 pub fn minimap(&self) -> Option<&Entity<Self>> {
19351 self.minimap
19352 .as_ref()
19353 .filter(|_| self.minimap_visibility.visible())
19354 }
19355
19356 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19357 let mut wrap_guides = smallvec![];
19358
19359 if self.show_wrap_guides == Some(false) {
19360 return wrap_guides;
19361 }
19362
19363 let settings = self.buffer.read(cx).language_settings(cx);
19364 if settings.show_wrap_guides {
19365 match self.soft_wrap_mode(cx) {
19366 SoftWrap::Column(soft_wrap) => {
19367 wrap_guides.push((soft_wrap as usize, true));
19368 }
19369 SoftWrap::Bounded(soft_wrap) => {
19370 wrap_guides.push((soft_wrap as usize, true));
19371 }
19372 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19373 }
19374 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19375 }
19376
19377 wrap_guides
19378 }
19379
19380 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19381 let settings = self.buffer.read(cx).language_settings(cx);
19382 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19383 match mode {
19384 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19385 SoftWrap::None
19386 }
19387 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19388 language_settings::SoftWrap::PreferredLineLength => {
19389 SoftWrap::Column(settings.preferred_line_length)
19390 }
19391 language_settings::SoftWrap::Bounded => {
19392 SoftWrap::Bounded(settings.preferred_line_length)
19393 }
19394 }
19395 }
19396
19397 pub fn set_soft_wrap_mode(
19398 &mut self,
19399 mode: language_settings::SoftWrap,
19400
19401 cx: &mut Context<Self>,
19402 ) {
19403 self.soft_wrap_mode_override = Some(mode);
19404 cx.notify();
19405 }
19406
19407 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19408 self.hard_wrap = hard_wrap;
19409 cx.notify();
19410 }
19411
19412 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19413 self.text_style_refinement = Some(style);
19414 }
19415
19416 /// called by the Element so we know what style we were most recently rendered with.
19417 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19418 // We intentionally do not inform the display map about the minimap style
19419 // so that wrapping is not recalculated and stays consistent for the editor
19420 // and its linked minimap.
19421 if !self.mode.is_minimap() {
19422 let font = style.text.font();
19423 let font_size = style.text.font_size.to_pixels(window.rem_size());
19424 let display_map = self
19425 .placeholder_display_map
19426 .as_ref()
19427 .filter(|_| self.is_empty(cx))
19428 .unwrap_or(&self.display_map);
19429
19430 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19431 }
19432 self.style = Some(style);
19433 }
19434
19435 pub fn style(&self) -> Option<&EditorStyle> {
19436 self.style.as_ref()
19437 }
19438
19439 // Called by the element. This method is not designed to be called outside of the editor
19440 // element's layout code because it does not notify when rewrapping is computed synchronously.
19441 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19442 if self.is_empty(cx) {
19443 self.placeholder_display_map
19444 .as_ref()
19445 .map_or(false, |display_map| {
19446 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19447 })
19448 } else {
19449 self.display_map
19450 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19451 }
19452 }
19453
19454 pub fn set_soft_wrap(&mut self) {
19455 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19456 }
19457
19458 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19459 if self.soft_wrap_mode_override.is_some() {
19460 self.soft_wrap_mode_override.take();
19461 } else {
19462 let soft_wrap = match self.soft_wrap_mode(cx) {
19463 SoftWrap::GitDiff => return,
19464 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19465 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19466 language_settings::SoftWrap::None
19467 }
19468 };
19469 self.soft_wrap_mode_override = Some(soft_wrap);
19470 }
19471 cx.notify();
19472 }
19473
19474 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19475 let Some(workspace) = self.workspace() else {
19476 return;
19477 };
19478 let fs = workspace.read(cx).app_state().fs.clone();
19479 let current_show = TabBarSettings::get_global(cx).show;
19480 update_settings_file(fs, cx, move |setting, _| {
19481 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19482 });
19483 }
19484
19485 pub fn toggle_indent_guides(
19486 &mut self,
19487 _: &ToggleIndentGuides,
19488 _: &mut Window,
19489 cx: &mut Context<Self>,
19490 ) {
19491 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19492 self.buffer
19493 .read(cx)
19494 .language_settings(cx)
19495 .indent_guides
19496 .enabled
19497 });
19498 self.show_indent_guides = Some(!currently_enabled);
19499 cx.notify();
19500 }
19501
19502 fn should_show_indent_guides(&self) -> Option<bool> {
19503 self.show_indent_guides
19504 }
19505
19506 pub fn toggle_line_numbers(
19507 &mut self,
19508 _: &ToggleLineNumbers,
19509 _: &mut Window,
19510 cx: &mut Context<Self>,
19511 ) {
19512 let mut editor_settings = EditorSettings::get_global(cx).clone();
19513 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19514 EditorSettings::override_global(editor_settings, cx);
19515 }
19516
19517 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19518 if let Some(show_line_numbers) = self.show_line_numbers {
19519 return show_line_numbers;
19520 }
19521 EditorSettings::get_global(cx).gutter.line_numbers
19522 }
19523
19524 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19525 self.use_relative_line_numbers
19526 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19527 }
19528
19529 pub fn toggle_relative_line_numbers(
19530 &mut self,
19531 _: &ToggleRelativeLineNumbers,
19532 _: &mut Window,
19533 cx: &mut Context<Self>,
19534 ) {
19535 let is_relative = self.should_use_relative_line_numbers(cx);
19536 self.set_relative_line_number(Some(!is_relative), cx)
19537 }
19538
19539 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19540 self.use_relative_line_numbers = is_relative;
19541 cx.notify();
19542 }
19543
19544 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19545 self.show_gutter = show_gutter;
19546 cx.notify();
19547 }
19548
19549 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19550 self.show_scrollbars = ScrollbarAxes {
19551 horizontal: show,
19552 vertical: show,
19553 };
19554 cx.notify();
19555 }
19556
19557 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19558 self.show_scrollbars.vertical = show;
19559 cx.notify();
19560 }
19561
19562 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19563 self.show_scrollbars.horizontal = show;
19564 cx.notify();
19565 }
19566
19567 pub fn set_minimap_visibility(
19568 &mut self,
19569 minimap_visibility: MinimapVisibility,
19570 window: &mut Window,
19571 cx: &mut Context<Self>,
19572 ) {
19573 if self.minimap_visibility != minimap_visibility {
19574 if minimap_visibility.visible() && self.minimap.is_none() {
19575 let minimap_settings = EditorSettings::get_global(cx).minimap;
19576 self.minimap =
19577 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19578 }
19579 self.minimap_visibility = minimap_visibility;
19580 cx.notify();
19581 }
19582 }
19583
19584 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19585 self.set_show_scrollbars(false, cx);
19586 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19587 }
19588
19589 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19590 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19591 }
19592
19593 /// Normally the text in full mode and auto height editors is padded on the
19594 /// left side by roughly half a character width for improved hit testing.
19595 ///
19596 /// Use this method to disable this for cases where this is not wanted (e.g.
19597 /// if you want to align the editor text with some other text above or below)
19598 /// or if you want to add this padding to single-line editors.
19599 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19600 self.offset_content = offset_content;
19601 cx.notify();
19602 }
19603
19604 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19605 self.show_line_numbers = Some(show_line_numbers);
19606 cx.notify();
19607 }
19608
19609 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19610 self.disable_expand_excerpt_buttons = true;
19611 cx.notify();
19612 }
19613
19614 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19615 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19616 cx.notify();
19617 }
19618
19619 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19620 self.show_code_actions = Some(show_code_actions);
19621 cx.notify();
19622 }
19623
19624 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19625 self.show_runnables = Some(show_runnables);
19626 cx.notify();
19627 }
19628
19629 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19630 self.show_breakpoints = Some(show_breakpoints);
19631 cx.notify();
19632 }
19633
19634 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19635 if self.display_map.read(cx).masked != masked {
19636 self.display_map.update(cx, |map, _| map.masked = masked);
19637 }
19638 cx.notify()
19639 }
19640
19641 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19642 self.show_wrap_guides = Some(show_wrap_guides);
19643 cx.notify();
19644 }
19645
19646 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19647 self.show_indent_guides = Some(show_indent_guides);
19648 cx.notify();
19649 }
19650
19651 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19652 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19653 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19654 && let Some(dir) = file.abs_path(cx).parent()
19655 {
19656 return Some(dir.to_owned());
19657 }
19658 }
19659
19660 None
19661 }
19662
19663 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19664 self.active_excerpt(cx)?
19665 .1
19666 .read(cx)
19667 .file()
19668 .and_then(|f| f.as_local())
19669 }
19670
19671 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19672 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19673 let buffer = buffer.read(cx);
19674 if let Some(project_path) = buffer.project_path(cx) {
19675 let project = self.project()?.read(cx);
19676 project.absolute_path(&project_path, cx)
19677 } else {
19678 buffer
19679 .file()
19680 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19681 }
19682 })
19683 }
19684
19685 pub fn reveal_in_finder(
19686 &mut self,
19687 _: &RevealInFileManager,
19688 _window: &mut Window,
19689 cx: &mut Context<Self>,
19690 ) {
19691 if let Some(target) = self.target_file(cx) {
19692 cx.reveal_path(&target.abs_path(cx));
19693 }
19694 }
19695
19696 pub fn copy_path(
19697 &mut self,
19698 _: &zed_actions::workspace::CopyPath,
19699 _window: &mut Window,
19700 cx: &mut Context<Self>,
19701 ) {
19702 if let Some(path) = self.target_file_abs_path(cx)
19703 && let Some(path) = path.to_str()
19704 {
19705 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19706 } else {
19707 cx.propagate();
19708 }
19709 }
19710
19711 pub fn copy_relative_path(
19712 &mut self,
19713 _: &zed_actions::workspace::CopyRelativePath,
19714 _window: &mut Window,
19715 cx: &mut Context<Self>,
19716 ) {
19717 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19718 let project = self.project()?.read(cx);
19719 let path = buffer.read(cx).file()?.path();
19720 let path = path.display(project.path_style(cx));
19721 Some(path)
19722 }) {
19723 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19724 } else {
19725 cx.propagate();
19726 }
19727 }
19728
19729 /// Returns the project path for the editor's buffer, if any buffer is
19730 /// opened in the editor.
19731 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19732 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19733 buffer.read(cx).project_path(cx)
19734 } else {
19735 None
19736 }
19737 }
19738
19739 // Returns true if the editor handled a go-to-line request
19740 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19741 maybe!({
19742 let breakpoint_store = self.breakpoint_store.as_ref()?;
19743
19744 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19745 else {
19746 self.clear_row_highlights::<ActiveDebugLine>();
19747 return None;
19748 };
19749
19750 let position = active_stack_frame.position;
19751 let buffer_id = position.buffer_id?;
19752 let snapshot = self
19753 .project
19754 .as_ref()?
19755 .read(cx)
19756 .buffer_for_id(buffer_id, cx)?
19757 .read(cx)
19758 .snapshot();
19759
19760 let mut handled = false;
19761 for (id, ExcerptRange { context, .. }) in
19762 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19763 {
19764 if context.start.cmp(&position, &snapshot).is_ge()
19765 || context.end.cmp(&position, &snapshot).is_lt()
19766 {
19767 continue;
19768 }
19769 let snapshot = self.buffer.read(cx).snapshot(cx);
19770 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19771
19772 handled = true;
19773 self.clear_row_highlights::<ActiveDebugLine>();
19774
19775 self.go_to_line::<ActiveDebugLine>(
19776 multibuffer_anchor,
19777 Some(cx.theme().colors().editor_debugger_active_line_background),
19778 window,
19779 cx,
19780 );
19781
19782 cx.notify();
19783 }
19784
19785 handled.then_some(())
19786 })
19787 .is_some()
19788 }
19789
19790 pub fn copy_file_name_without_extension(
19791 &mut self,
19792 _: &CopyFileNameWithoutExtension,
19793 _: &mut Window,
19794 cx: &mut Context<Self>,
19795 ) {
19796 if let Some(file) = self.target_file(cx)
19797 && let Some(file_stem) = file.path().file_stem()
19798 {
19799 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19800 }
19801 }
19802
19803 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19804 if let Some(file) = self.target_file(cx)
19805 && let Some(name) = file.path().file_name()
19806 {
19807 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19808 }
19809 }
19810
19811 pub fn toggle_git_blame(
19812 &mut self,
19813 _: &::git::Blame,
19814 window: &mut Window,
19815 cx: &mut Context<Self>,
19816 ) {
19817 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19818
19819 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19820 self.start_git_blame(true, window, cx);
19821 }
19822
19823 cx.notify();
19824 }
19825
19826 pub fn toggle_git_blame_inline(
19827 &mut self,
19828 _: &ToggleGitBlameInline,
19829 window: &mut Window,
19830 cx: &mut Context<Self>,
19831 ) {
19832 self.toggle_git_blame_inline_internal(true, window, cx);
19833 cx.notify();
19834 }
19835
19836 pub fn open_git_blame_commit(
19837 &mut self,
19838 _: &OpenGitBlameCommit,
19839 window: &mut Window,
19840 cx: &mut Context<Self>,
19841 ) {
19842 self.open_git_blame_commit_internal(window, cx);
19843 }
19844
19845 fn open_git_blame_commit_internal(
19846 &mut self,
19847 window: &mut Window,
19848 cx: &mut Context<Self>,
19849 ) -> Option<()> {
19850 let blame = self.blame.as_ref()?;
19851 let snapshot = self.snapshot(window, cx);
19852 let cursor = self
19853 .selections
19854 .newest::<Point>(&snapshot.display_snapshot)
19855 .head();
19856 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19857 let (_, blame_entry) = blame
19858 .update(cx, |blame, cx| {
19859 blame
19860 .blame_for_rows(
19861 &[RowInfo {
19862 buffer_id: Some(buffer.remote_id()),
19863 buffer_row: Some(point.row),
19864 ..Default::default()
19865 }],
19866 cx,
19867 )
19868 .next()
19869 })
19870 .flatten()?;
19871 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19872 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19873 let workspace = self.workspace()?.downgrade();
19874 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19875 None
19876 }
19877
19878 pub fn git_blame_inline_enabled(&self) -> bool {
19879 self.git_blame_inline_enabled
19880 }
19881
19882 pub fn toggle_selection_menu(
19883 &mut self,
19884 _: &ToggleSelectionMenu,
19885 _: &mut Window,
19886 cx: &mut Context<Self>,
19887 ) {
19888 self.show_selection_menu = self
19889 .show_selection_menu
19890 .map(|show_selections_menu| !show_selections_menu)
19891 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19892
19893 cx.notify();
19894 }
19895
19896 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19897 self.show_selection_menu
19898 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19899 }
19900
19901 fn start_git_blame(
19902 &mut self,
19903 user_triggered: bool,
19904 window: &mut Window,
19905 cx: &mut Context<Self>,
19906 ) {
19907 if let Some(project) = self.project() {
19908 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19909 && buffer.read(cx).file().is_none()
19910 {
19911 return;
19912 }
19913
19914 let focused = self.focus_handle(cx).contains_focused(window, cx);
19915
19916 let project = project.clone();
19917 let blame = cx
19918 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19919 self.blame_subscription =
19920 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19921 self.blame = Some(blame);
19922 }
19923 }
19924
19925 fn toggle_git_blame_inline_internal(
19926 &mut self,
19927 user_triggered: bool,
19928 window: &mut Window,
19929 cx: &mut Context<Self>,
19930 ) {
19931 if self.git_blame_inline_enabled {
19932 self.git_blame_inline_enabled = false;
19933 self.show_git_blame_inline = false;
19934 self.show_git_blame_inline_delay_task.take();
19935 } else {
19936 self.git_blame_inline_enabled = true;
19937 self.start_git_blame_inline(user_triggered, window, cx);
19938 }
19939
19940 cx.notify();
19941 }
19942
19943 fn start_git_blame_inline(
19944 &mut self,
19945 user_triggered: bool,
19946 window: &mut Window,
19947 cx: &mut Context<Self>,
19948 ) {
19949 self.start_git_blame(user_triggered, window, cx);
19950
19951 if ProjectSettings::get_global(cx)
19952 .git
19953 .inline_blame_delay()
19954 .is_some()
19955 {
19956 self.start_inline_blame_timer(window, cx);
19957 } else {
19958 self.show_git_blame_inline = true
19959 }
19960 }
19961
19962 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19963 self.blame.as_ref()
19964 }
19965
19966 pub fn show_git_blame_gutter(&self) -> bool {
19967 self.show_git_blame_gutter
19968 }
19969
19970 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19971 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19972 }
19973
19974 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19975 self.show_git_blame_inline
19976 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19977 && !self.newest_selection_head_on_empty_line(cx)
19978 && self.has_blame_entries(cx)
19979 }
19980
19981 fn has_blame_entries(&self, cx: &App) -> bool {
19982 self.blame()
19983 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19984 }
19985
19986 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19987 let cursor_anchor = self.selections.newest_anchor().head();
19988
19989 let snapshot = self.buffer.read(cx).snapshot(cx);
19990 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19991
19992 snapshot.line_len(buffer_row) == 0
19993 }
19994
19995 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19996 let buffer_and_selection = maybe!({
19997 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19998 let selection_range = selection.range();
19999
20000 let multi_buffer = self.buffer().read(cx);
20001 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20002 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20003
20004 let (buffer, range, _) = if selection.reversed {
20005 buffer_ranges.first()
20006 } else {
20007 buffer_ranges.last()
20008 }?;
20009
20010 let selection = text::ToPoint::to_point(&range.start, buffer).row
20011 ..text::ToPoint::to_point(&range.end, buffer).row;
20012 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20013 });
20014
20015 let Some((buffer, selection)) = buffer_and_selection else {
20016 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20017 };
20018
20019 let Some(project) = self.project() else {
20020 return Task::ready(Err(anyhow!("editor does not have project")));
20021 };
20022
20023 project.update(cx, |project, cx| {
20024 project.get_permalink_to_line(&buffer, selection, cx)
20025 })
20026 }
20027
20028 pub fn copy_permalink_to_line(
20029 &mut self,
20030 _: &CopyPermalinkToLine,
20031 window: &mut Window,
20032 cx: &mut Context<Self>,
20033 ) {
20034 let permalink_task = self.get_permalink_to_line(cx);
20035 let workspace = self.workspace();
20036
20037 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20038 Ok(permalink) => {
20039 cx.update(|_, cx| {
20040 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20041 })
20042 .ok();
20043 }
20044 Err(err) => {
20045 let message = format!("Failed to copy permalink: {err}");
20046
20047 anyhow::Result::<()>::Err(err).log_err();
20048
20049 if let Some(workspace) = workspace {
20050 workspace
20051 .update_in(cx, |workspace, _, cx| {
20052 struct CopyPermalinkToLine;
20053
20054 workspace.show_toast(
20055 Toast::new(
20056 NotificationId::unique::<CopyPermalinkToLine>(),
20057 message,
20058 ),
20059 cx,
20060 )
20061 })
20062 .ok();
20063 }
20064 }
20065 })
20066 .detach();
20067 }
20068
20069 pub fn copy_file_location(
20070 &mut self,
20071 _: &CopyFileLocation,
20072 _: &mut Window,
20073 cx: &mut Context<Self>,
20074 ) {
20075 let selection = self
20076 .selections
20077 .newest::<Point>(&self.display_snapshot(cx))
20078 .start
20079 .row
20080 + 1;
20081 if let Some(file) = self.target_file(cx) {
20082 let path = file.path().display(file.path_style(cx));
20083 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20084 }
20085 }
20086
20087 pub fn open_permalink_to_line(
20088 &mut self,
20089 _: &OpenPermalinkToLine,
20090 window: &mut Window,
20091 cx: &mut Context<Self>,
20092 ) {
20093 let permalink_task = self.get_permalink_to_line(cx);
20094 let workspace = self.workspace();
20095
20096 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20097 Ok(permalink) => {
20098 cx.update(|_, cx| {
20099 cx.open_url(permalink.as_ref());
20100 })
20101 .ok();
20102 }
20103 Err(err) => {
20104 let message = format!("Failed to open permalink: {err}");
20105
20106 anyhow::Result::<()>::Err(err).log_err();
20107
20108 if let Some(workspace) = workspace {
20109 workspace
20110 .update(cx, |workspace, cx| {
20111 struct OpenPermalinkToLine;
20112
20113 workspace.show_toast(
20114 Toast::new(
20115 NotificationId::unique::<OpenPermalinkToLine>(),
20116 message,
20117 ),
20118 cx,
20119 )
20120 })
20121 .ok();
20122 }
20123 }
20124 })
20125 .detach();
20126 }
20127
20128 pub fn insert_uuid_v4(
20129 &mut self,
20130 _: &InsertUuidV4,
20131 window: &mut Window,
20132 cx: &mut Context<Self>,
20133 ) {
20134 self.insert_uuid(UuidVersion::V4, window, cx);
20135 }
20136
20137 pub fn insert_uuid_v7(
20138 &mut self,
20139 _: &InsertUuidV7,
20140 window: &mut Window,
20141 cx: &mut Context<Self>,
20142 ) {
20143 self.insert_uuid(UuidVersion::V7, window, cx);
20144 }
20145
20146 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20147 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20148 self.transact(window, cx, |this, window, cx| {
20149 let edits = this
20150 .selections
20151 .all::<Point>(&this.display_snapshot(cx))
20152 .into_iter()
20153 .map(|selection| {
20154 let uuid = match version {
20155 UuidVersion::V4 => uuid::Uuid::new_v4(),
20156 UuidVersion::V7 => uuid::Uuid::now_v7(),
20157 };
20158
20159 (selection.range(), uuid.to_string())
20160 });
20161 this.edit(edits, cx);
20162 this.refresh_edit_prediction(true, false, window, cx);
20163 });
20164 }
20165
20166 pub fn open_selections_in_multibuffer(
20167 &mut self,
20168 _: &OpenSelectionsInMultibuffer,
20169 window: &mut Window,
20170 cx: &mut Context<Self>,
20171 ) {
20172 let multibuffer = self.buffer.read(cx);
20173
20174 let Some(buffer) = multibuffer.as_singleton() else {
20175 return;
20176 };
20177
20178 let Some(workspace) = self.workspace() else {
20179 return;
20180 };
20181
20182 let title = multibuffer.title(cx).to_string();
20183
20184 let locations = self
20185 .selections
20186 .all_anchors(cx)
20187 .iter()
20188 .map(|selection| {
20189 (
20190 buffer.clone(),
20191 (selection.start.text_anchor..selection.end.text_anchor)
20192 .to_point(buffer.read(cx)),
20193 )
20194 })
20195 .into_group_map();
20196
20197 cx.spawn_in(window, async move |_, cx| {
20198 workspace.update_in(cx, |workspace, window, cx| {
20199 Self::open_locations_in_multibuffer(
20200 workspace,
20201 locations,
20202 format!("Selections for '{title}'"),
20203 false,
20204 MultibufferSelectionMode::All,
20205 window,
20206 cx,
20207 );
20208 })
20209 })
20210 .detach();
20211 }
20212
20213 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20214 /// last highlight added will be used.
20215 ///
20216 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20217 pub fn highlight_rows<T: 'static>(
20218 &mut self,
20219 range: Range<Anchor>,
20220 color: Hsla,
20221 options: RowHighlightOptions,
20222 cx: &mut Context<Self>,
20223 ) {
20224 let snapshot = self.buffer().read(cx).snapshot(cx);
20225 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20226 let ix = row_highlights.binary_search_by(|highlight| {
20227 Ordering::Equal
20228 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20229 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20230 });
20231
20232 if let Err(mut ix) = ix {
20233 let index = post_inc(&mut self.highlight_order);
20234
20235 // If this range intersects with the preceding highlight, then merge it with
20236 // the preceding highlight. Otherwise insert a new highlight.
20237 let mut merged = false;
20238 if ix > 0 {
20239 let prev_highlight = &mut row_highlights[ix - 1];
20240 if prev_highlight
20241 .range
20242 .end
20243 .cmp(&range.start, &snapshot)
20244 .is_ge()
20245 {
20246 ix -= 1;
20247 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20248 prev_highlight.range.end = range.end;
20249 }
20250 merged = true;
20251 prev_highlight.index = index;
20252 prev_highlight.color = color;
20253 prev_highlight.options = options;
20254 }
20255 }
20256
20257 if !merged {
20258 row_highlights.insert(
20259 ix,
20260 RowHighlight {
20261 range,
20262 index,
20263 color,
20264 options,
20265 type_id: TypeId::of::<T>(),
20266 },
20267 );
20268 }
20269
20270 // If any of the following highlights intersect with this one, merge them.
20271 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20272 let highlight = &row_highlights[ix];
20273 if next_highlight
20274 .range
20275 .start
20276 .cmp(&highlight.range.end, &snapshot)
20277 .is_le()
20278 {
20279 if next_highlight
20280 .range
20281 .end
20282 .cmp(&highlight.range.end, &snapshot)
20283 .is_gt()
20284 {
20285 row_highlights[ix].range.end = next_highlight.range.end;
20286 }
20287 row_highlights.remove(ix + 1);
20288 } else {
20289 break;
20290 }
20291 }
20292 }
20293 }
20294
20295 /// Remove any highlighted row ranges of the given type that intersect the
20296 /// given ranges.
20297 pub fn remove_highlighted_rows<T: 'static>(
20298 &mut self,
20299 ranges_to_remove: Vec<Range<Anchor>>,
20300 cx: &mut Context<Self>,
20301 ) {
20302 let snapshot = self.buffer().read(cx).snapshot(cx);
20303 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20304 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20305 row_highlights.retain(|highlight| {
20306 while let Some(range_to_remove) = ranges_to_remove.peek() {
20307 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20308 Ordering::Less | Ordering::Equal => {
20309 ranges_to_remove.next();
20310 }
20311 Ordering::Greater => {
20312 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20313 Ordering::Less | Ordering::Equal => {
20314 return false;
20315 }
20316 Ordering::Greater => break,
20317 }
20318 }
20319 }
20320 }
20321
20322 true
20323 })
20324 }
20325
20326 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20327 pub fn clear_row_highlights<T: 'static>(&mut self) {
20328 self.highlighted_rows.remove(&TypeId::of::<T>());
20329 }
20330
20331 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20332 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20333 self.highlighted_rows
20334 .get(&TypeId::of::<T>())
20335 .map_or(&[] as &[_], |vec| vec.as_slice())
20336 .iter()
20337 .map(|highlight| (highlight.range.clone(), highlight.color))
20338 }
20339
20340 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20341 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20342 /// Allows to ignore certain kinds of highlights.
20343 pub fn highlighted_display_rows(
20344 &self,
20345 window: &mut Window,
20346 cx: &mut App,
20347 ) -> BTreeMap<DisplayRow, LineHighlight> {
20348 let snapshot = self.snapshot(window, cx);
20349 let mut used_highlight_orders = HashMap::default();
20350 self.highlighted_rows
20351 .iter()
20352 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20353 .fold(
20354 BTreeMap::<DisplayRow, LineHighlight>::new(),
20355 |mut unique_rows, highlight| {
20356 let start = highlight.range.start.to_display_point(&snapshot);
20357 let end = highlight.range.end.to_display_point(&snapshot);
20358 let start_row = start.row().0;
20359 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20360 && end.column() == 0
20361 {
20362 end.row().0.saturating_sub(1)
20363 } else {
20364 end.row().0
20365 };
20366 for row in start_row..=end_row {
20367 let used_index =
20368 used_highlight_orders.entry(row).or_insert(highlight.index);
20369 if highlight.index >= *used_index {
20370 *used_index = highlight.index;
20371 unique_rows.insert(
20372 DisplayRow(row),
20373 LineHighlight {
20374 include_gutter: highlight.options.include_gutter,
20375 border: None,
20376 background: highlight.color.into(),
20377 type_id: Some(highlight.type_id),
20378 },
20379 );
20380 }
20381 }
20382 unique_rows
20383 },
20384 )
20385 }
20386
20387 pub fn highlighted_display_row_for_autoscroll(
20388 &self,
20389 snapshot: &DisplaySnapshot,
20390 ) -> Option<DisplayRow> {
20391 self.highlighted_rows
20392 .values()
20393 .flat_map(|highlighted_rows| highlighted_rows.iter())
20394 .filter_map(|highlight| {
20395 if highlight.options.autoscroll {
20396 Some(highlight.range.start.to_display_point(snapshot).row())
20397 } else {
20398 None
20399 }
20400 })
20401 .min()
20402 }
20403
20404 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20405 self.highlight_background::<SearchWithinRange>(
20406 ranges,
20407 |colors| colors.colors().editor_document_highlight_read_background,
20408 cx,
20409 )
20410 }
20411
20412 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20413 self.breadcrumb_header = Some(new_header);
20414 }
20415
20416 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20417 self.clear_background_highlights::<SearchWithinRange>(cx);
20418 }
20419
20420 pub fn highlight_background<T: 'static>(
20421 &mut self,
20422 ranges: &[Range<Anchor>],
20423 color_fetcher: fn(&Theme) -> Hsla,
20424 cx: &mut Context<Self>,
20425 ) {
20426 self.background_highlights.insert(
20427 HighlightKey::Type(TypeId::of::<T>()),
20428 (color_fetcher, Arc::from(ranges)),
20429 );
20430 self.scrollbar_marker_state.dirty = true;
20431 cx.notify();
20432 }
20433
20434 pub fn highlight_background_key<T: 'static>(
20435 &mut self,
20436 key: usize,
20437 ranges: &[Range<Anchor>],
20438 color_fetcher: fn(&Theme) -> Hsla,
20439 cx: &mut Context<Self>,
20440 ) {
20441 self.background_highlights.insert(
20442 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20443 (color_fetcher, Arc::from(ranges)),
20444 );
20445 self.scrollbar_marker_state.dirty = true;
20446 cx.notify();
20447 }
20448
20449 pub fn clear_background_highlights<T: 'static>(
20450 &mut self,
20451 cx: &mut Context<Self>,
20452 ) -> Option<BackgroundHighlight> {
20453 let text_highlights = self
20454 .background_highlights
20455 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20456 if !text_highlights.1.is_empty() {
20457 self.scrollbar_marker_state.dirty = true;
20458 cx.notify();
20459 }
20460 Some(text_highlights)
20461 }
20462
20463 pub fn highlight_gutter<T: 'static>(
20464 &mut self,
20465 ranges: impl Into<Vec<Range<Anchor>>>,
20466 color_fetcher: fn(&App) -> Hsla,
20467 cx: &mut Context<Self>,
20468 ) {
20469 self.gutter_highlights
20470 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20471 cx.notify();
20472 }
20473
20474 pub fn clear_gutter_highlights<T: 'static>(
20475 &mut self,
20476 cx: &mut Context<Self>,
20477 ) -> Option<GutterHighlight> {
20478 cx.notify();
20479 self.gutter_highlights.remove(&TypeId::of::<T>())
20480 }
20481
20482 pub fn insert_gutter_highlight<T: 'static>(
20483 &mut self,
20484 range: Range<Anchor>,
20485 color_fetcher: fn(&App) -> Hsla,
20486 cx: &mut Context<Self>,
20487 ) {
20488 let snapshot = self.buffer().read(cx).snapshot(cx);
20489 let mut highlights = self
20490 .gutter_highlights
20491 .remove(&TypeId::of::<T>())
20492 .map(|(_, highlights)| highlights)
20493 .unwrap_or_default();
20494 let ix = highlights.binary_search_by(|highlight| {
20495 Ordering::Equal
20496 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20497 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20498 });
20499 if let Err(ix) = ix {
20500 highlights.insert(ix, range);
20501 }
20502 self.gutter_highlights
20503 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20504 }
20505
20506 pub fn remove_gutter_highlights<T: 'static>(
20507 &mut self,
20508 ranges_to_remove: Vec<Range<Anchor>>,
20509 cx: &mut Context<Self>,
20510 ) {
20511 let snapshot = self.buffer().read(cx).snapshot(cx);
20512 let Some((color_fetcher, mut gutter_highlights)) =
20513 self.gutter_highlights.remove(&TypeId::of::<T>())
20514 else {
20515 return;
20516 };
20517 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20518 gutter_highlights.retain(|highlight| {
20519 while let Some(range_to_remove) = ranges_to_remove.peek() {
20520 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20521 Ordering::Less | Ordering::Equal => {
20522 ranges_to_remove.next();
20523 }
20524 Ordering::Greater => {
20525 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20526 Ordering::Less | Ordering::Equal => {
20527 return false;
20528 }
20529 Ordering::Greater => break,
20530 }
20531 }
20532 }
20533 }
20534
20535 true
20536 });
20537 self.gutter_highlights
20538 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20539 }
20540
20541 #[cfg(feature = "test-support")]
20542 pub fn all_text_highlights(
20543 &self,
20544 window: &mut Window,
20545 cx: &mut Context<Self>,
20546 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20547 let snapshot = self.snapshot(window, cx);
20548 self.display_map.update(cx, |display_map, _| {
20549 display_map
20550 .all_text_highlights()
20551 .map(|highlight| {
20552 let (style, ranges) = highlight.as_ref();
20553 (
20554 *style,
20555 ranges
20556 .iter()
20557 .map(|range| range.clone().to_display_points(&snapshot))
20558 .collect(),
20559 )
20560 })
20561 .collect()
20562 })
20563 }
20564
20565 #[cfg(feature = "test-support")]
20566 pub fn all_text_background_highlights(
20567 &self,
20568 window: &mut Window,
20569 cx: &mut Context<Self>,
20570 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20571 let snapshot = self.snapshot(window, cx);
20572 let buffer = &snapshot.buffer_snapshot();
20573 let start = buffer.anchor_before(0);
20574 let end = buffer.anchor_after(buffer.len());
20575 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20576 }
20577
20578 #[cfg(any(test, feature = "test-support"))]
20579 pub fn sorted_background_highlights_in_range(
20580 &self,
20581 search_range: Range<Anchor>,
20582 display_snapshot: &DisplaySnapshot,
20583 theme: &Theme,
20584 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20585 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20586 res.sort_by(|a, b| {
20587 a.0.start
20588 .cmp(&b.0.start)
20589 .then_with(|| a.0.end.cmp(&b.0.end))
20590 .then_with(|| a.1.cmp(&b.1))
20591 });
20592 res
20593 }
20594
20595 #[cfg(feature = "test-support")]
20596 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20597 let snapshot = self.buffer().read(cx).snapshot(cx);
20598
20599 let highlights = self
20600 .background_highlights
20601 .get(&HighlightKey::Type(TypeId::of::<
20602 items::BufferSearchHighlights,
20603 >()));
20604
20605 if let Some((_color, ranges)) = highlights {
20606 ranges
20607 .iter()
20608 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20609 .collect_vec()
20610 } else {
20611 vec![]
20612 }
20613 }
20614
20615 fn document_highlights_for_position<'a>(
20616 &'a self,
20617 position: Anchor,
20618 buffer: &'a MultiBufferSnapshot,
20619 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20620 let read_highlights = self
20621 .background_highlights
20622 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20623 .map(|h| &h.1);
20624 let write_highlights = self
20625 .background_highlights
20626 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20627 .map(|h| &h.1);
20628 let left_position = position.bias_left(buffer);
20629 let right_position = position.bias_right(buffer);
20630 read_highlights
20631 .into_iter()
20632 .chain(write_highlights)
20633 .flat_map(move |ranges| {
20634 let start_ix = match ranges.binary_search_by(|probe| {
20635 let cmp = probe.end.cmp(&left_position, buffer);
20636 if cmp.is_ge() {
20637 Ordering::Greater
20638 } else {
20639 Ordering::Less
20640 }
20641 }) {
20642 Ok(i) | Err(i) => i,
20643 };
20644
20645 ranges[start_ix..]
20646 .iter()
20647 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20648 })
20649 }
20650
20651 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20652 self.background_highlights
20653 .get(&HighlightKey::Type(TypeId::of::<T>()))
20654 .is_some_and(|(_, highlights)| !highlights.is_empty())
20655 }
20656
20657 /// Returns all background highlights for a given range.
20658 ///
20659 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20660 pub fn background_highlights_in_range(
20661 &self,
20662 search_range: Range<Anchor>,
20663 display_snapshot: &DisplaySnapshot,
20664 theme: &Theme,
20665 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20666 let mut results = Vec::new();
20667 for (color_fetcher, ranges) in self.background_highlights.values() {
20668 let color = color_fetcher(theme);
20669 let start_ix = match ranges.binary_search_by(|probe| {
20670 let cmp = probe
20671 .end
20672 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20673 if cmp.is_gt() {
20674 Ordering::Greater
20675 } else {
20676 Ordering::Less
20677 }
20678 }) {
20679 Ok(i) | Err(i) => i,
20680 };
20681 for range in &ranges[start_ix..] {
20682 if range
20683 .start
20684 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20685 .is_ge()
20686 {
20687 break;
20688 }
20689
20690 let start = range.start.to_display_point(display_snapshot);
20691 let end = range.end.to_display_point(display_snapshot);
20692 results.push((start..end, color))
20693 }
20694 }
20695 results
20696 }
20697
20698 pub fn gutter_highlights_in_range(
20699 &self,
20700 search_range: Range<Anchor>,
20701 display_snapshot: &DisplaySnapshot,
20702 cx: &App,
20703 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20704 let mut results = Vec::new();
20705 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20706 let color = color_fetcher(cx);
20707 let start_ix = match ranges.binary_search_by(|probe| {
20708 let cmp = probe
20709 .end
20710 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20711 if cmp.is_gt() {
20712 Ordering::Greater
20713 } else {
20714 Ordering::Less
20715 }
20716 }) {
20717 Ok(i) | Err(i) => i,
20718 };
20719 for range in &ranges[start_ix..] {
20720 if range
20721 .start
20722 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20723 .is_ge()
20724 {
20725 break;
20726 }
20727
20728 let start = range.start.to_display_point(display_snapshot);
20729 let end = range.end.to_display_point(display_snapshot);
20730 results.push((start..end, color))
20731 }
20732 }
20733 results
20734 }
20735
20736 /// Get the text ranges corresponding to the redaction query
20737 pub fn redacted_ranges(
20738 &self,
20739 search_range: Range<Anchor>,
20740 display_snapshot: &DisplaySnapshot,
20741 cx: &App,
20742 ) -> Vec<Range<DisplayPoint>> {
20743 display_snapshot
20744 .buffer_snapshot()
20745 .redacted_ranges(search_range, |file| {
20746 if let Some(file) = file {
20747 file.is_private()
20748 && EditorSettings::get(
20749 Some(SettingsLocation {
20750 worktree_id: file.worktree_id(cx),
20751 path: file.path().as_ref(),
20752 }),
20753 cx,
20754 )
20755 .redact_private_values
20756 } else {
20757 false
20758 }
20759 })
20760 .map(|range| {
20761 range.start.to_display_point(display_snapshot)
20762 ..range.end.to_display_point(display_snapshot)
20763 })
20764 .collect()
20765 }
20766
20767 pub fn highlight_text_key<T: 'static>(
20768 &mut self,
20769 key: usize,
20770 ranges: Vec<Range<Anchor>>,
20771 style: HighlightStyle,
20772 cx: &mut Context<Self>,
20773 ) {
20774 self.display_map.update(cx, |map, _| {
20775 map.highlight_text(
20776 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20777 ranges,
20778 style,
20779 );
20780 });
20781 cx.notify();
20782 }
20783
20784 pub fn highlight_text<T: 'static>(
20785 &mut self,
20786 ranges: Vec<Range<Anchor>>,
20787 style: HighlightStyle,
20788 cx: &mut Context<Self>,
20789 ) {
20790 self.display_map.update(cx, |map, _| {
20791 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20792 });
20793 cx.notify();
20794 }
20795
20796 pub fn text_highlights<'a, T: 'static>(
20797 &'a self,
20798 cx: &'a App,
20799 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20800 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20801 }
20802
20803 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20804 let cleared = self
20805 .display_map
20806 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20807 if cleared {
20808 cx.notify();
20809 }
20810 }
20811
20812 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20813 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20814 && self.focus_handle.is_focused(window)
20815 }
20816
20817 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20818 self.show_cursor_when_unfocused = is_enabled;
20819 cx.notify();
20820 }
20821
20822 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20823 cx.notify();
20824 }
20825
20826 fn on_debug_session_event(
20827 &mut self,
20828 _session: Entity<Session>,
20829 event: &SessionEvent,
20830 cx: &mut Context<Self>,
20831 ) {
20832 if let SessionEvent::InvalidateInlineValue = event {
20833 self.refresh_inline_values(cx);
20834 }
20835 }
20836
20837 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20838 let Some(project) = self.project.clone() else {
20839 return;
20840 };
20841
20842 if !self.inline_value_cache.enabled {
20843 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20844 self.splice_inlays(&inlays, Vec::new(), cx);
20845 return;
20846 }
20847
20848 let current_execution_position = self
20849 .highlighted_rows
20850 .get(&TypeId::of::<ActiveDebugLine>())
20851 .and_then(|lines| lines.last().map(|line| line.range.end));
20852
20853 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20854 let inline_values = editor
20855 .update(cx, |editor, cx| {
20856 let Some(current_execution_position) = current_execution_position else {
20857 return Some(Task::ready(Ok(Vec::new())));
20858 };
20859
20860 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20861 let snapshot = buffer.snapshot(cx);
20862
20863 let excerpt = snapshot.excerpt_containing(
20864 current_execution_position..current_execution_position,
20865 )?;
20866
20867 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20868 })?;
20869
20870 let range =
20871 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20872
20873 project.inline_values(buffer, range, cx)
20874 })
20875 .ok()
20876 .flatten()?
20877 .await
20878 .context("refreshing debugger inlays")
20879 .log_err()?;
20880
20881 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20882
20883 for (buffer_id, inline_value) in inline_values
20884 .into_iter()
20885 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20886 {
20887 buffer_inline_values
20888 .entry(buffer_id)
20889 .or_default()
20890 .push(inline_value);
20891 }
20892
20893 editor
20894 .update(cx, |editor, cx| {
20895 let snapshot = editor.buffer.read(cx).snapshot(cx);
20896 let mut new_inlays = Vec::default();
20897
20898 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20899 let buffer_id = buffer_snapshot.remote_id();
20900 buffer_inline_values
20901 .get(&buffer_id)
20902 .into_iter()
20903 .flatten()
20904 .for_each(|hint| {
20905 let inlay = Inlay::debugger(
20906 post_inc(&mut editor.next_inlay_id),
20907 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20908 hint.text(),
20909 );
20910 if !inlay.text().chars().contains(&'\n') {
20911 new_inlays.push(inlay);
20912 }
20913 });
20914 }
20915
20916 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20917 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20918
20919 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20920 })
20921 .ok()?;
20922 Some(())
20923 });
20924 }
20925
20926 fn on_buffer_event(
20927 &mut self,
20928 multibuffer: &Entity<MultiBuffer>,
20929 event: &multi_buffer::Event,
20930 window: &mut Window,
20931 cx: &mut Context<Self>,
20932 ) {
20933 match event {
20934 multi_buffer::Event::Edited { edited_buffer } => {
20935 self.scrollbar_marker_state.dirty = true;
20936 self.active_indent_guides_state.dirty = true;
20937 self.refresh_active_diagnostics(cx);
20938 self.refresh_code_actions(window, cx);
20939 self.refresh_selected_text_highlights(true, window, cx);
20940 self.refresh_single_line_folds(window, cx);
20941 self.refresh_matching_bracket_highlights(window, cx);
20942 if self.has_active_edit_prediction() {
20943 self.update_visible_edit_prediction(window, cx);
20944 }
20945
20946 if let Some(buffer) = edited_buffer {
20947 if buffer.read(cx).file().is_none() {
20948 cx.emit(EditorEvent::TitleChanged);
20949 }
20950
20951 if self.project.is_some() {
20952 let buffer_id = buffer.read(cx).remote_id();
20953 self.register_buffer(buffer_id, cx);
20954 self.update_lsp_data(Some(buffer_id), window, cx);
20955 self.refresh_inlay_hints(
20956 InlayHintRefreshReason::BufferEdited(buffer_id),
20957 cx,
20958 );
20959 }
20960 }
20961
20962 cx.emit(EditorEvent::BufferEdited);
20963 cx.emit(SearchEvent::MatchesInvalidated);
20964
20965 let Some(project) = &self.project else { return };
20966 let (telemetry, is_via_ssh) = {
20967 let project = project.read(cx);
20968 let telemetry = project.client().telemetry().clone();
20969 let is_via_ssh = project.is_via_remote_server();
20970 (telemetry, is_via_ssh)
20971 };
20972 telemetry.log_edit_event("editor", is_via_ssh);
20973 }
20974 multi_buffer::Event::ExcerptsAdded {
20975 buffer,
20976 predecessor,
20977 excerpts,
20978 } => {
20979 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20980 let buffer_id = buffer.read(cx).remote_id();
20981 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20982 && let Some(project) = &self.project
20983 {
20984 update_uncommitted_diff_for_buffer(
20985 cx.entity(),
20986 project,
20987 [buffer.clone()],
20988 self.buffer.clone(),
20989 cx,
20990 )
20991 .detach();
20992 }
20993 self.update_lsp_data(Some(buffer_id), window, cx);
20994 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20995 cx.emit(EditorEvent::ExcerptsAdded {
20996 buffer: buffer.clone(),
20997 predecessor: *predecessor,
20998 excerpts: excerpts.clone(),
20999 });
21000 }
21001 multi_buffer::Event::ExcerptsRemoved {
21002 ids,
21003 removed_buffer_ids,
21004 } => {
21005 if let Some(inlay_hints) = &mut self.inlay_hints {
21006 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21007 }
21008 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21009 for buffer_id in removed_buffer_ids {
21010 self.registered_buffers.remove(buffer_id);
21011 }
21012 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21013 cx.emit(EditorEvent::ExcerptsRemoved {
21014 ids: ids.clone(),
21015 removed_buffer_ids: removed_buffer_ids.clone(),
21016 });
21017 }
21018 multi_buffer::Event::ExcerptsEdited {
21019 excerpt_ids,
21020 buffer_ids,
21021 } => {
21022 self.display_map.update(cx, |map, cx| {
21023 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21024 });
21025 cx.emit(EditorEvent::ExcerptsEdited {
21026 ids: excerpt_ids.clone(),
21027 });
21028 }
21029 multi_buffer::Event::ExcerptsExpanded { ids } => {
21030 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21031 self.refresh_document_highlights(cx);
21032 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21033 }
21034 multi_buffer::Event::Reparsed(buffer_id) => {
21035 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21036 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21037
21038 cx.emit(EditorEvent::Reparsed(*buffer_id));
21039 }
21040 multi_buffer::Event::DiffHunksToggled => {
21041 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21042 }
21043 multi_buffer::Event::LanguageChanged(buffer_id) => {
21044 self.registered_buffers.remove(&buffer_id);
21045 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21046 cx.emit(EditorEvent::Reparsed(*buffer_id));
21047 cx.notify();
21048 }
21049 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21050 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21051 multi_buffer::Event::FileHandleChanged
21052 | multi_buffer::Event::Reloaded
21053 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21054 multi_buffer::Event::DiagnosticsUpdated => {
21055 self.update_diagnostics_state(window, cx);
21056 }
21057 _ => {}
21058 };
21059 }
21060
21061 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21062 if !self.diagnostics_enabled() {
21063 return;
21064 }
21065 self.refresh_active_diagnostics(cx);
21066 self.refresh_inline_diagnostics(true, window, cx);
21067 self.scrollbar_marker_state.dirty = true;
21068 cx.notify();
21069 }
21070
21071 pub fn start_temporary_diff_override(&mut self) {
21072 self.load_diff_task.take();
21073 self.temporary_diff_override = true;
21074 }
21075
21076 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21077 self.temporary_diff_override = false;
21078 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21079 self.buffer.update(cx, |buffer, cx| {
21080 buffer.set_all_diff_hunks_collapsed(cx);
21081 });
21082
21083 if let Some(project) = self.project.clone() {
21084 self.load_diff_task = Some(
21085 update_uncommitted_diff_for_buffer(
21086 cx.entity(),
21087 &project,
21088 self.buffer.read(cx).all_buffers(),
21089 self.buffer.clone(),
21090 cx,
21091 )
21092 .shared(),
21093 );
21094 }
21095 }
21096
21097 fn on_display_map_changed(
21098 &mut self,
21099 _: Entity<DisplayMap>,
21100 _: &mut Window,
21101 cx: &mut Context<Self>,
21102 ) {
21103 cx.notify();
21104 }
21105
21106 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21107 if self.diagnostics_enabled() {
21108 let new_severity = EditorSettings::get_global(cx)
21109 .diagnostics_max_severity
21110 .unwrap_or(DiagnosticSeverity::Hint);
21111 self.set_max_diagnostics_severity(new_severity, cx);
21112 }
21113 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21114 self.update_edit_prediction_settings(cx);
21115 self.refresh_edit_prediction(true, false, window, cx);
21116 self.refresh_inline_values(cx);
21117 self.refresh_inlay_hints(
21118 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21119 self.selections.newest_anchor().head(),
21120 &self.buffer.read(cx).snapshot(cx),
21121 cx,
21122 )),
21123 cx,
21124 );
21125
21126 let old_cursor_shape = self.cursor_shape;
21127 let old_show_breadcrumbs = self.show_breadcrumbs;
21128
21129 {
21130 let editor_settings = EditorSettings::get_global(cx);
21131 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21132 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21133 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21134 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21135 }
21136
21137 if old_cursor_shape != self.cursor_shape {
21138 cx.emit(EditorEvent::CursorShapeChanged);
21139 }
21140
21141 if old_show_breadcrumbs != self.show_breadcrumbs {
21142 cx.emit(EditorEvent::BreadcrumbsChanged);
21143 }
21144
21145 let project_settings = ProjectSettings::get_global(cx);
21146 self.serialize_dirty_buffers =
21147 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21148
21149 if self.mode.is_full() {
21150 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21151 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21152 if self.show_inline_diagnostics != show_inline_diagnostics {
21153 self.show_inline_diagnostics = show_inline_diagnostics;
21154 self.refresh_inline_diagnostics(false, window, cx);
21155 }
21156
21157 if self.git_blame_inline_enabled != inline_blame_enabled {
21158 self.toggle_git_blame_inline_internal(false, window, cx);
21159 }
21160
21161 let minimap_settings = EditorSettings::get_global(cx).minimap;
21162 if self.minimap_visibility != MinimapVisibility::Disabled {
21163 if self.minimap_visibility.settings_visibility()
21164 != minimap_settings.minimap_enabled()
21165 {
21166 self.set_minimap_visibility(
21167 MinimapVisibility::for_mode(self.mode(), cx),
21168 window,
21169 cx,
21170 );
21171 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21172 minimap_entity.update(cx, |minimap_editor, cx| {
21173 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21174 })
21175 }
21176 }
21177 }
21178
21179 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21180 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21181 }) {
21182 if !inlay_splice.is_empty() {
21183 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21184 }
21185 self.refresh_colors_for_visible_range(None, window, cx);
21186 }
21187
21188 cx.notify();
21189 }
21190
21191 pub fn set_searchable(&mut self, searchable: bool) {
21192 self.searchable = searchable;
21193 }
21194
21195 pub fn searchable(&self) -> bool {
21196 self.searchable
21197 }
21198
21199 pub fn open_excerpts_in_split(
21200 &mut self,
21201 _: &OpenExcerptsSplit,
21202 window: &mut Window,
21203 cx: &mut Context<Self>,
21204 ) {
21205 self.open_excerpts_common(None, true, window, cx)
21206 }
21207
21208 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21209 self.open_excerpts_common(None, false, window, cx)
21210 }
21211
21212 fn open_excerpts_common(
21213 &mut self,
21214 jump_data: Option<JumpData>,
21215 split: bool,
21216 window: &mut Window,
21217 cx: &mut Context<Self>,
21218 ) {
21219 let Some(workspace) = self.workspace() else {
21220 cx.propagate();
21221 return;
21222 };
21223
21224 if self.buffer.read(cx).is_singleton() {
21225 cx.propagate();
21226 return;
21227 }
21228
21229 let mut new_selections_by_buffer = HashMap::default();
21230 match &jump_data {
21231 Some(JumpData::MultiBufferPoint {
21232 excerpt_id,
21233 position,
21234 anchor,
21235 line_offset_from_top,
21236 }) => {
21237 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21238 if let Some(buffer) = multi_buffer_snapshot
21239 .buffer_id_for_excerpt(*excerpt_id)
21240 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21241 {
21242 let buffer_snapshot = buffer.read(cx).snapshot();
21243 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21244 language::ToPoint::to_point(anchor, &buffer_snapshot)
21245 } else {
21246 buffer_snapshot.clip_point(*position, Bias::Left)
21247 };
21248 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21249 new_selections_by_buffer.insert(
21250 buffer,
21251 (
21252 vec![jump_to_offset..jump_to_offset],
21253 Some(*line_offset_from_top),
21254 ),
21255 );
21256 }
21257 }
21258 Some(JumpData::MultiBufferRow {
21259 row,
21260 line_offset_from_top,
21261 }) => {
21262 let point = MultiBufferPoint::new(row.0, 0);
21263 if let Some((buffer, buffer_point, _)) =
21264 self.buffer.read(cx).point_to_buffer_point(point, cx)
21265 {
21266 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21267 new_selections_by_buffer
21268 .entry(buffer)
21269 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21270 .0
21271 .push(buffer_offset..buffer_offset)
21272 }
21273 }
21274 None => {
21275 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21276 let multi_buffer = self.buffer.read(cx);
21277 for selection in selections {
21278 for (snapshot, range, _, anchor) in multi_buffer
21279 .snapshot(cx)
21280 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21281 {
21282 if let Some(anchor) = anchor {
21283 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21284 else {
21285 continue;
21286 };
21287 let offset = text::ToOffset::to_offset(
21288 &anchor.text_anchor,
21289 &buffer_handle.read(cx).snapshot(),
21290 );
21291 let range = offset..offset;
21292 new_selections_by_buffer
21293 .entry(buffer_handle)
21294 .or_insert((Vec::new(), None))
21295 .0
21296 .push(range)
21297 } else {
21298 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21299 else {
21300 continue;
21301 };
21302 new_selections_by_buffer
21303 .entry(buffer_handle)
21304 .or_insert((Vec::new(), None))
21305 .0
21306 .push(range)
21307 }
21308 }
21309 }
21310 }
21311 }
21312
21313 new_selections_by_buffer
21314 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21315
21316 if new_selections_by_buffer.is_empty() {
21317 return;
21318 }
21319
21320 // We defer the pane interaction because we ourselves are a workspace item
21321 // and activating a new item causes the pane to call a method on us reentrantly,
21322 // which panics if we're on the stack.
21323 window.defer(cx, move |window, cx| {
21324 workspace.update(cx, |workspace, cx| {
21325 let pane = if split {
21326 workspace.adjacent_pane(window, cx)
21327 } else {
21328 workspace.active_pane().clone()
21329 };
21330
21331 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21332 let editor = buffer
21333 .read(cx)
21334 .file()
21335 .is_none()
21336 .then(|| {
21337 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21338 // so `workspace.open_project_item` will never find them, always opening a new editor.
21339 // Instead, we try to activate the existing editor in the pane first.
21340 let (editor, pane_item_index) =
21341 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21342 let editor = item.downcast::<Editor>()?;
21343 let singleton_buffer =
21344 editor.read(cx).buffer().read(cx).as_singleton()?;
21345 if singleton_buffer == buffer {
21346 Some((editor, i))
21347 } else {
21348 None
21349 }
21350 })?;
21351 pane.update(cx, |pane, cx| {
21352 pane.activate_item(pane_item_index, true, true, window, cx)
21353 });
21354 Some(editor)
21355 })
21356 .flatten()
21357 .unwrap_or_else(|| {
21358 workspace.open_project_item::<Self>(
21359 pane.clone(),
21360 buffer,
21361 true,
21362 true,
21363 window,
21364 cx,
21365 )
21366 });
21367
21368 editor.update(cx, |editor, cx| {
21369 let autoscroll = match scroll_offset {
21370 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21371 None => Autoscroll::newest(),
21372 };
21373 let nav_history = editor.nav_history.take();
21374 editor.change_selections(
21375 SelectionEffects::scroll(autoscroll),
21376 window,
21377 cx,
21378 |s| {
21379 s.select_ranges(ranges);
21380 },
21381 );
21382 editor.nav_history = nav_history;
21383 });
21384 }
21385 })
21386 });
21387 }
21388
21389 // For now, don't allow opening excerpts in buffers that aren't backed by
21390 // regular project files.
21391 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21392 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21393 }
21394
21395 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21396 let snapshot = self.buffer.read(cx).read(cx);
21397 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21398 Some(
21399 ranges
21400 .iter()
21401 .map(move |range| {
21402 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21403 })
21404 .collect(),
21405 )
21406 }
21407
21408 fn selection_replacement_ranges(
21409 &self,
21410 range: Range<OffsetUtf16>,
21411 cx: &mut App,
21412 ) -> Vec<Range<OffsetUtf16>> {
21413 let selections = self
21414 .selections
21415 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21416 let newest_selection = selections
21417 .iter()
21418 .max_by_key(|selection| selection.id)
21419 .unwrap();
21420 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21421 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21422 let snapshot = self.buffer.read(cx).read(cx);
21423 selections
21424 .into_iter()
21425 .map(|mut selection| {
21426 selection.start.0 =
21427 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21428 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21429 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21430 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21431 })
21432 .collect()
21433 }
21434
21435 fn report_editor_event(
21436 &self,
21437 reported_event: ReportEditorEvent,
21438 file_extension: Option<String>,
21439 cx: &App,
21440 ) {
21441 if cfg!(any(test, feature = "test-support")) {
21442 return;
21443 }
21444
21445 let Some(project) = &self.project else { return };
21446
21447 // If None, we are in a file without an extension
21448 let file = self
21449 .buffer
21450 .read(cx)
21451 .as_singleton()
21452 .and_then(|b| b.read(cx).file());
21453 let file_extension = file_extension.or(file
21454 .as_ref()
21455 .and_then(|file| Path::new(file.file_name(cx)).extension())
21456 .and_then(|e| e.to_str())
21457 .map(|a| a.to_string()));
21458
21459 let vim_mode = vim_flavor(cx).is_some();
21460
21461 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21462 let copilot_enabled = edit_predictions_provider
21463 == language::language_settings::EditPredictionProvider::Copilot;
21464 let copilot_enabled_for_language = self
21465 .buffer
21466 .read(cx)
21467 .language_settings(cx)
21468 .show_edit_predictions;
21469
21470 let project = project.read(cx);
21471 let event_type = reported_event.event_type();
21472
21473 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21474 telemetry::event!(
21475 event_type,
21476 type = if auto_saved {"autosave"} else {"manual"},
21477 file_extension,
21478 vim_mode,
21479 copilot_enabled,
21480 copilot_enabled_for_language,
21481 edit_predictions_provider,
21482 is_via_ssh = project.is_via_remote_server(),
21483 );
21484 } else {
21485 telemetry::event!(
21486 event_type,
21487 file_extension,
21488 vim_mode,
21489 copilot_enabled,
21490 copilot_enabled_for_language,
21491 edit_predictions_provider,
21492 is_via_ssh = project.is_via_remote_server(),
21493 );
21494 };
21495 }
21496
21497 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21498 /// with each line being an array of {text, highlight} objects.
21499 fn copy_highlight_json(
21500 &mut self,
21501 _: &CopyHighlightJson,
21502 window: &mut Window,
21503 cx: &mut Context<Self>,
21504 ) {
21505 #[derive(Serialize)]
21506 struct Chunk<'a> {
21507 text: String,
21508 highlight: Option<&'a str>,
21509 }
21510
21511 let snapshot = self.buffer.read(cx).snapshot(cx);
21512 let range = self
21513 .selected_text_range(false, window, cx)
21514 .and_then(|selection| {
21515 if selection.range.is_empty() {
21516 None
21517 } else {
21518 Some(
21519 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21520 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21521 )
21522 }
21523 })
21524 .unwrap_or_else(|| 0..snapshot.len());
21525
21526 let chunks = snapshot.chunks(range, true);
21527 let mut lines = Vec::new();
21528 let mut line: VecDeque<Chunk> = VecDeque::new();
21529
21530 let Some(style) = self.style.as_ref() else {
21531 return;
21532 };
21533
21534 for chunk in chunks {
21535 let highlight = chunk
21536 .syntax_highlight_id
21537 .and_then(|id| id.name(&style.syntax));
21538 let mut chunk_lines = chunk.text.split('\n').peekable();
21539 while let Some(text) = chunk_lines.next() {
21540 let mut merged_with_last_token = false;
21541 if let Some(last_token) = line.back_mut()
21542 && last_token.highlight == highlight
21543 {
21544 last_token.text.push_str(text);
21545 merged_with_last_token = true;
21546 }
21547
21548 if !merged_with_last_token {
21549 line.push_back(Chunk {
21550 text: text.into(),
21551 highlight,
21552 });
21553 }
21554
21555 if chunk_lines.peek().is_some() {
21556 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21557 line.pop_front();
21558 }
21559 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21560 line.pop_back();
21561 }
21562
21563 lines.push(mem::take(&mut line));
21564 }
21565 }
21566 }
21567
21568 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21569 return;
21570 };
21571 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21572 }
21573
21574 pub fn open_context_menu(
21575 &mut self,
21576 _: &OpenContextMenu,
21577 window: &mut Window,
21578 cx: &mut Context<Self>,
21579 ) {
21580 self.request_autoscroll(Autoscroll::newest(), cx);
21581 let position = self
21582 .selections
21583 .newest_display(&self.display_snapshot(cx))
21584 .start;
21585 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21586 }
21587
21588 pub fn replay_insert_event(
21589 &mut self,
21590 text: &str,
21591 relative_utf16_range: Option<Range<isize>>,
21592 window: &mut Window,
21593 cx: &mut Context<Self>,
21594 ) {
21595 if !self.input_enabled {
21596 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21597 return;
21598 }
21599 if let Some(relative_utf16_range) = relative_utf16_range {
21600 let selections = self
21601 .selections
21602 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21603 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21604 let new_ranges = selections.into_iter().map(|range| {
21605 let start = OffsetUtf16(
21606 range
21607 .head()
21608 .0
21609 .saturating_add_signed(relative_utf16_range.start),
21610 );
21611 let end = OffsetUtf16(
21612 range
21613 .head()
21614 .0
21615 .saturating_add_signed(relative_utf16_range.end),
21616 );
21617 start..end
21618 });
21619 s.select_ranges(new_ranges);
21620 });
21621 }
21622
21623 self.handle_input(text, window, cx);
21624 }
21625
21626 pub fn is_focused(&self, window: &Window) -> bool {
21627 self.focus_handle.is_focused(window)
21628 }
21629
21630 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21631 cx.emit(EditorEvent::Focused);
21632
21633 if let Some(descendant) = self
21634 .last_focused_descendant
21635 .take()
21636 .and_then(|descendant| descendant.upgrade())
21637 {
21638 window.focus(&descendant);
21639 } else {
21640 if let Some(blame) = self.blame.as_ref() {
21641 blame.update(cx, GitBlame::focus)
21642 }
21643
21644 self.blink_manager.update(cx, BlinkManager::enable);
21645 self.show_cursor_names(window, cx);
21646 self.buffer.update(cx, |buffer, cx| {
21647 buffer.finalize_last_transaction(cx);
21648 if self.leader_id.is_none() {
21649 buffer.set_active_selections(
21650 &self.selections.disjoint_anchors_arc(),
21651 self.selections.line_mode(),
21652 self.cursor_shape,
21653 cx,
21654 );
21655 }
21656 });
21657 }
21658 }
21659
21660 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21661 cx.emit(EditorEvent::FocusedIn)
21662 }
21663
21664 fn handle_focus_out(
21665 &mut self,
21666 event: FocusOutEvent,
21667 _window: &mut Window,
21668 cx: &mut Context<Self>,
21669 ) {
21670 if event.blurred != self.focus_handle {
21671 self.last_focused_descendant = Some(event.blurred);
21672 }
21673 self.selection_drag_state = SelectionDragState::None;
21674 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21675 }
21676
21677 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21678 self.blink_manager.update(cx, BlinkManager::disable);
21679 self.buffer
21680 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21681
21682 if let Some(blame) = self.blame.as_ref() {
21683 blame.update(cx, GitBlame::blur)
21684 }
21685 if !self.hover_state.focused(window, cx) {
21686 hide_hover(self, cx);
21687 }
21688 if !self
21689 .context_menu
21690 .borrow()
21691 .as_ref()
21692 .is_some_and(|context_menu| context_menu.focused(window, cx))
21693 {
21694 self.hide_context_menu(window, cx);
21695 }
21696 self.take_active_edit_prediction(cx);
21697 cx.emit(EditorEvent::Blurred);
21698 cx.notify();
21699 }
21700
21701 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21702 let mut pending: String = window
21703 .pending_input_keystrokes()
21704 .into_iter()
21705 .flatten()
21706 .filter_map(|keystroke| {
21707 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21708 keystroke.key_char.clone()
21709 } else {
21710 None
21711 }
21712 })
21713 .collect();
21714
21715 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21716 pending = "".to_string();
21717 }
21718
21719 let existing_pending = self
21720 .text_highlights::<PendingInput>(cx)
21721 .map(|(_, ranges)| ranges.to_vec());
21722 if existing_pending.is_none() && pending.is_empty() {
21723 return;
21724 }
21725 let transaction =
21726 self.transact(window, cx, |this, window, cx| {
21727 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21728 let edits = selections
21729 .iter()
21730 .map(|selection| (selection.end..selection.end, pending.clone()));
21731 this.edit(edits, cx);
21732 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21733 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21734 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21735 }));
21736 });
21737 if let Some(existing_ranges) = existing_pending {
21738 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21739 this.edit(edits, cx);
21740 }
21741 });
21742
21743 let snapshot = self.snapshot(window, cx);
21744 let ranges = self
21745 .selections
21746 .all::<usize>(&snapshot.display_snapshot)
21747 .into_iter()
21748 .map(|selection| {
21749 snapshot.buffer_snapshot().anchor_after(selection.end)
21750 ..snapshot
21751 .buffer_snapshot()
21752 .anchor_before(selection.end + pending.len())
21753 })
21754 .collect();
21755
21756 if pending.is_empty() {
21757 self.clear_highlights::<PendingInput>(cx);
21758 } else {
21759 self.highlight_text::<PendingInput>(
21760 ranges,
21761 HighlightStyle {
21762 underline: Some(UnderlineStyle {
21763 thickness: px(1.),
21764 color: None,
21765 wavy: false,
21766 }),
21767 ..Default::default()
21768 },
21769 cx,
21770 );
21771 }
21772
21773 self.ime_transaction = self.ime_transaction.or(transaction);
21774 if let Some(transaction) = self.ime_transaction {
21775 self.buffer.update(cx, |buffer, cx| {
21776 buffer.group_until_transaction(transaction, cx);
21777 });
21778 }
21779
21780 if self.text_highlights::<PendingInput>(cx).is_none() {
21781 self.ime_transaction.take();
21782 }
21783 }
21784
21785 pub fn register_action_renderer(
21786 &mut self,
21787 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21788 ) -> Subscription {
21789 let id = self.next_editor_action_id.post_inc();
21790 self.editor_actions
21791 .borrow_mut()
21792 .insert(id, Box::new(listener));
21793
21794 let editor_actions = self.editor_actions.clone();
21795 Subscription::new(move || {
21796 editor_actions.borrow_mut().remove(&id);
21797 })
21798 }
21799
21800 pub fn register_action<A: Action>(
21801 &mut self,
21802 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21803 ) -> Subscription {
21804 let id = self.next_editor_action_id.post_inc();
21805 let listener = Arc::new(listener);
21806 self.editor_actions.borrow_mut().insert(
21807 id,
21808 Box::new(move |_, window, _| {
21809 let listener = listener.clone();
21810 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21811 let action = action.downcast_ref().unwrap();
21812 if phase == DispatchPhase::Bubble {
21813 listener(action, window, cx)
21814 }
21815 })
21816 }),
21817 );
21818
21819 let editor_actions = self.editor_actions.clone();
21820 Subscription::new(move || {
21821 editor_actions.borrow_mut().remove(&id);
21822 })
21823 }
21824
21825 pub fn file_header_size(&self) -> u32 {
21826 FILE_HEADER_HEIGHT
21827 }
21828
21829 pub fn restore(
21830 &mut self,
21831 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21832 window: &mut Window,
21833 cx: &mut Context<Self>,
21834 ) {
21835 let workspace = self.workspace();
21836 let project = self.project();
21837 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21838 let mut tasks = Vec::new();
21839 for (buffer_id, changes) in revert_changes {
21840 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21841 buffer.update(cx, |buffer, cx| {
21842 buffer.edit(
21843 changes
21844 .into_iter()
21845 .map(|(range, text)| (range, text.to_string())),
21846 None,
21847 cx,
21848 );
21849 });
21850
21851 if let Some(project) =
21852 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21853 {
21854 project.update(cx, |project, cx| {
21855 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21856 })
21857 }
21858 }
21859 }
21860 tasks
21861 });
21862 cx.spawn_in(window, async move |_, cx| {
21863 for (buffer, task) in save_tasks {
21864 let result = task.await;
21865 if result.is_err() {
21866 let Some(path) = buffer
21867 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21868 .ok()
21869 else {
21870 continue;
21871 };
21872 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21873 let Some(task) = cx
21874 .update_window_entity(workspace, |workspace, window, cx| {
21875 workspace
21876 .open_path_preview(path, None, false, false, false, window, cx)
21877 })
21878 .ok()
21879 else {
21880 continue;
21881 };
21882 task.await.log_err();
21883 }
21884 }
21885 }
21886 })
21887 .detach();
21888 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21889 selections.refresh()
21890 });
21891 }
21892
21893 pub fn to_pixel_point(
21894 &self,
21895 source: multi_buffer::Anchor,
21896 editor_snapshot: &EditorSnapshot,
21897 window: &mut Window,
21898 ) -> Option<gpui::Point<Pixels>> {
21899 let source_point = source.to_display_point(editor_snapshot);
21900 self.display_to_pixel_point(source_point, editor_snapshot, window)
21901 }
21902
21903 pub fn display_to_pixel_point(
21904 &self,
21905 source: DisplayPoint,
21906 editor_snapshot: &EditorSnapshot,
21907 window: &mut Window,
21908 ) -> Option<gpui::Point<Pixels>> {
21909 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21910 let text_layout_details = self.text_layout_details(window);
21911 let scroll_top = text_layout_details
21912 .scroll_anchor
21913 .scroll_position(editor_snapshot)
21914 .y;
21915
21916 if source.row().as_f64() < scroll_top.floor() {
21917 return None;
21918 }
21919 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21920 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21921 Some(gpui::Point::new(source_x, source_y))
21922 }
21923
21924 pub fn has_visible_completions_menu(&self) -> bool {
21925 !self.edit_prediction_preview_is_active()
21926 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21927 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21928 })
21929 }
21930
21931 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21932 if self.mode.is_minimap() {
21933 return;
21934 }
21935 self.addons
21936 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21937 }
21938
21939 pub fn unregister_addon<T: Addon>(&mut self) {
21940 self.addons.remove(&std::any::TypeId::of::<T>());
21941 }
21942
21943 pub fn addon<T: Addon>(&self) -> Option<&T> {
21944 let type_id = std::any::TypeId::of::<T>();
21945 self.addons
21946 .get(&type_id)
21947 .and_then(|item| item.to_any().downcast_ref::<T>())
21948 }
21949
21950 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21951 let type_id = std::any::TypeId::of::<T>();
21952 self.addons
21953 .get_mut(&type_id)
21954 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21955 }
21956
21957 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21958 let text_layout_details = self.text_layout_details(window);
21959 let style = &text_layout_details.editor_style;
21960 let font_id = window.text_system().resolve_font(&style.text.font());
21961 let font_size = style.text.font_size.to_pixels(window.rem_size());
21962 let line_height = style.text.line_height_in_pixels(window.rem_size());
21963 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21964 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21965
21966 CharacterDimensions {
21967 em_width,
21968 em_advance,
21969 line_height,
21970 }
21971 }
21972
21973 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21974 self.load_diff_task.clone()
21975 }
21976
21977 fn read_metadata_from_db(
21978 &mut self,
21979 item_id: u64,
21980 workspace_id: WorkspaceId,
21981 window: &mut Window,
21982 cx: &mut Context<Editor>,
21983 ) {
21984 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21985 && !self.mode.is_minimap()
21986 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21987 {
21988 let buffer_snapshot = OnceCell::new();
21989
21990 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21991 && !folds.is_empty()
21992 {
21993 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21994 self.fold_ranges(
21995 folds
21996 .into_iter()
21997 .map(|(start, end)| {
21998 snapshot.clip_offset(start, Bias::Left)
21999 ..snapshot.clip_offset(end, Bias::Right)
22000 })
22001 .collect(),
22002 false,
22003 window,
22004 cx,
22005 );
22006 }
22007
22008 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22009 && !selections.is_empty()
22010 {
22011 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22012 // skip adding the initial selection to selection history
22013 self.selection_history.mode = SelectionHistoryMode::Skipping;
22014 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22015 s.select_ranges(selections.into_iter().map(|(start, end)| {
22016 snapshot.clip_offset(start, Bias::Left)
22017 ..snapshot.clip_offset(end, Bias::Right)
22018 }));
22019 });
22020 self.selection_history.mode = SelectionHistoryMode::Normal;
22021 };
22022 }
22023
22024 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22025 }
22026
22027 fn update_lsp_data(
22028 &mut self,
22029 for_buffer: Option<BufferId>,
22030 window: &mut Window,
22031 cx: &mut Context<'_, Self>,
22032 ) {
22033 self.pull_diagnostics(for_buffer, window, cx);
22034 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22035 }
22036
22037 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22038 if self.ignore_lsp_data() {
22039 return;
22040 }
22041 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22042 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22043 }
22044 }
22045
22046 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22047 if !self.registered_buffers.contains_key(&buffer_id)
22048 && let Some(project) = self.project.as_ref()
22049 {
22050 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22051 project.update(cx, |project, cx| {
22052 self.registered_buffers.insert(
22053 buffer_id,
22054 project.register_buffer_with_language_servers(&buffer, cx),
22055 );
22056 });
22057 } else {
22058 self.registered_buffers.remove(&buffer_id);
22059 }
22060 }
22061 }
22062
22063 fn ignore_lsp_data(&self) -> bool {
22064 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22065 // skip any LSP updates for it.
22066 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22067 }
22068}
22069
22070fn edit_for_markdown_paste<'a>(
22071 buffer: &MultiBufferSnapshot,
22072 range: Range<usize>,
22073 to_insert: &'a str,
22074 url: Option<url::Url>,
22075) -> (Range<usize>, Cow<'a, str>) {
22076 if url.is_none() {
22077 return (range, Cow::Borrowed(to_insert));
22078 };
22079
22080 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22081
22082 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22083 Cow::Borrowed(to_insert)
22084 } else {
22085 Cow::Owned(format!("[{old_text}]({to_insert})"))
22086 };
22087 (range, new_text)
22088}
22089
22090#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22091pub enum VimFlavor {
22092 Vim,
22093 Helix,
22094}
22095
22096pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22097 if vim_mode_setting::HelixModeSetting::try_get(cx)
22098 .map(|helix_mode| helix_mode.0)
22099 .unwrap_or(false)
22100 {
22101 Some(VimFlavor::Helix)
22102 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22103 .map(|vim_mode| vim_mode.0)
22104 .unwrap_or(false)
22105 {
22106 Some(VimFlavor::Vim)
22107 } else {
22108 None // neither vim nor helix mode
22109 }
22110}
22111
22112fn process_completion_for_edit(
22113 completion: &Completion,
22114 intent: CompletionIntent,
22115 buffer: &Entity<Buffer>,
22116 cursor_position: &text::Anchor,
22117 cx: &mut Context<Editor>,
22118) -> CompletionEdit {
22119 let buffer = buffer.read(cx);
22120 let buffer_snapshot = buffer.snapshot();
22121 let (snippet, new_text) = if completion.is_snippet() {
22122 let mut snippet_source = completion.new_text.clone();
22123 // Workaround for typescript language server issues so that methods don't expand within
22124 // strings and functions with type expressions. The previous point is used because the query
22125 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22126 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22127 let previous_point = if previous_point.column > 0 {
22128 cursor_position.to_previous_offset(&buffer_snapshot)
22129 } else {
22130 cursor_position.to_offset(&buffer_snapshot)
22131 };
22132 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22133 && scope.prefers_label_for_snippet_in_completion()
22134 && let Some(label) = completion.label()
22135 && matches!(
22136 completion.kind(),
22137 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22138 )
22139 {
22140 snippet_source = label;
22141 }
22142 match Snippet::parse(&snippet_source).log_err() {
22143 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22144 None => (None, completion.new_text.clone()),
22145 }
22146 } else {
22147 (None, completion.new_text.clone())
22148 };
22149
22150 let mut range_to_replace = {
22151 let replace_range = &completion.replace_range;
22152 if let CompletionSource::Lsp {
22153 insert_range: Some(insert_range),
22154 ..
22155 } = &completion.source
22156 {
22157 debug_assert_eq!(
22158 insert_range.start, replace_range.start,
22159 "insert_range and replace_range should start at the same position"
22160 );
22161 debug_assert!(
22162 insert_range
22163 .start
22164 .cmp(cursor_position, &buffer_snapshot)
22165 .is_le(),
22166 "insert_range should start before or at cursor position"
22167 );
22168 debug_assert!(
22169 replace_range
22170 .start
22171 .cmp(cursor_position, &buffer_snapshot)
22172 .is_le(),
22173 "replace_range should start before or at cursor position"
22174 );
22175
22176 let should_replace = match intent {
22177 CompletionIntent::CompleteWithInsert => false,
22178 CompletionIntent::CompleteWithReplace => true,
22179 CompletionIntent::Complete | CompletionIntent::Compose => {
22180 let insert_mode =
22181 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22182 .completions
22183 .lsp_insert_mode;
22184 match insert_mode {
22185 LspInsertMode::Insert => false,
22186 LspInsertMode::Replace => true,
22187 LspInsertMode::ReplaceSubsequence => {
22188 let mut text_to_replace = buffer.chars_for_range(
22189 buffer.anchor_before(replace_range.start)
22190 ..buffer.anchor_after(replace_range.end),
22191 );
22192 let mut current_needle = text_to_replace.next();
22193 for haystack_ch in completion.label.text.chars() {
22194 if let Some(needle_ch) = current_needle
22195 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22196 {
22197 current_needle = text_to_replace.next();
22198 }
22199 }
22200 current_needle.is_none()
22201 }
22202 LspInsertMode::ReplaceSuffix => {
22203 if replace_range
22204 .end
22205 .cmp(cursor_position, &buffer_snapshot)
22206 .is_gt()
22207 {
22208 let range_after_cursor = *cursor_position..replace_range.end;
22209 let text_after_cursor = buffer
22210 .text_for_range(
22211 buffer.anchor_before(range_after_cursor.start)
22212 ..buffer.anchor_after(range_after_cursor.end),
22213 )
22214 .collect::<String>()
22215 .to_ascii_lowercase();
22216 completion
22217 .label
22218 .text
22219 .to_ascii_lowercase()
22220 .ends_with(&text_after_cursor)
22221 } else {
22222 true
22223 }
22224 }
22225 }
22226 }
22227 };
22228
22229 if should_replace {
22230 replace_range.clone()
22231 } else {
22232 insert_range.clone()
22233 }
22234 } else {
22235 replace_range.clone()
22236 }
22237 };
22238
22239 if range_to_replace
22240 .end
22241 .cmp(cursor_position, &buffer_snapshot)
22242 .is_lt()
22243 {
22244 range_to_replace.end = *cursor_position;
22245 }
22246
22247 CompletionEdit {
22248 new_text,
22249 replace_range: range_to_replace.to_offset(buffer),
22250 snippet,
22251 }
22252}
22253
22254struct CompletionEdit {
22255 new_text: String,
22256 replace_range: Range<usize>,
22257 snippet: Option<Snippet>,
22258}
22259
22260fn insert_extra_newline_brackets(
22261 buffer: &MultiBufferSnapshot,
22262 range: Range<usize>,
22263 language: &language::LanguageScope,
22264) -> bool {
22265 let leading_whitespace_len = buffer
22266 .reversed_chars_at(range.start)
22267 .take_while(|c| c.is_whitespace() && *c != '\n')
22268 .map(|c| c.len_utf8())
22269 .sum::<usize>();
22270 let trailing_whitespace_len = buffer
22271 .chars_at(range.end)
22272 .take_while(|c| c.is_whitespace() && *c != '\n')
22273 .map(|c| c.len_utf8())
22274 .sum::<usize>();
22275 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22276
22277 language.brackets().any(|(pair, enabled)| {
22278 let pair_start = pair.start.trim_end();
22279 let pair_end = pair.end.trim_start();
22280
22281 enabled
22282 && pair.newline
22283 && buffer.contains_str_at(range.end, pair_end)
22284 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22285 })
22286}
22287
22288fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22289 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22290 [(buffer, range, _)] => (*buffer, range.clone()),
22291 _ => return false,
22292 };
22293 let pair = {
22294 let mut result: Option<BracketMatch> = None;
22295
22296 for pair in buffer
22297 .all_bracket_ranges(range.clone())
22298 .filter(move |pair| {
22299 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22300 })
22301 {
22302 let len = pair.close_range.end - pair.open_range.start;
22303
22304 if let Some(existing) = &result {
22305 let existing_len = existing.close_range.end - existing.open_range.start;
22306 if len > existing_len {
22307 continue;
22308 }
22309 }
22310
22311 result = Some(pair);
22312 }
22313
22314 result
22315 };
22316 let Some(pair) = pair else {
22317 return false;
22318 };
22319 pair.newline_only
22320 && buffer
22321 .chars_for_range(pair.open_range.end..range.start)
22322 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22323 .all(|c| c.is_whitespace() && c != '\n')
22324}
22325
22326fn update_uncommitted_diff_for_buffer(
22327 editor: Entity<Editor>,
22328 project: &Entity<Project>,
22329 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22330 buffer: Entity<MultiBuffer>,
22331 cx: &mut App,
22332) -> Task<()> {
22333 let mut tasks = Vec::new();
22334 project.update(cx, |project, cx| {
22335 for buffer in buffers {
22336 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22337 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22338 }
22339 }
22340 });
22341 cx.spawn(async move |cx| {
22342 let diffs = future::join_all(tasks).await;
22343 if editor
22344 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22345 .unwrap_or(false)
22346 {
22347 return;
22348 }
22349
22350 buffer
22351 .update(cx, |buffer, cx| {
22352 for diff in diffs.into_iter().flatten() {
22353 buffer.add_diff(diff, cx);
22354 }
22355 })
22356 .ok();
22357 })
22358}
22359
22360fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22361 let tab_size = tab_size.get() as usize;
22362 let mut width = offset;
22363
22364 for ch in text.chars() {
22365 width += if ch == '\t' {
22366 tab_size - (width % tab_size)
22367 } else {
22368 1
22369 };
22370 }
22371
22372 width - offset
22373}
22374
22375#[cfg(test)]
22376mod tests {
22377 use super::*;
22378
22379 #[test]
22380 fn test_string_size_with_expanded_tabs() {
22381 let nz = |val| NonZeroU32::new(val).unwrap();
22382 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22383 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22384 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22385 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22386 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22387 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22388 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22389 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22390 }
22391}
22392
22393/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22394struct WordBreakingTokenizer<'a> {
22395 input: &'a str,
22396}
22397
22398impl<'a> WordBreakingTokenizer<'a> {
22399 fn new(input: &'a str) -> Self {
22400 Self { input }
22401 }
22402}
22403
22404fn is_char_ideographic(ch: char) -> bool {
22405 use unicode_script::Script::*;
22406 use unicode_script::UnicodeScript;
22407 matches!(ch.script(), Han | Tangut | Yi)
22408}
22409
22410fn is_grapheme_ideographic(text: &str) -> bool {
22411 text.chars().any(is_char_ideographic)
22412}
22413
22414fn is_grapheme_whitespace(text: &str) -> bool {
22415 text.chars().any(|x| x.is_whitespace())
22416}
22417
22418fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22419 text.chars()
22420 .next()
22421 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22422}
22423
22424#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22425enum WordBreakToken<'a> {
22426 Word { token: &'a str, grapheme_len: usize },
22427 InlineWhitespace { token: &'a str, grapheme_len: usize },
22428 Newline,
22429}
22430
22431impl<'a> Iterator for WordBreakingTokenizer<'a> {
22432 /// Yields a span, the count of graphemes in the token, and whether it was
22433 /// whitespace. Note that it also breaks at word boundaries.
22434 type Item = WordBreakToken<'a>;
22435
22436 fn next(&mut self) -> Option<Self::Item> {
22437 use unicode_segmentation::UnicodeSegmentation;
22438 if self.input.is_empty() {
22439 return None;
22440 }
22441
22442 let mut iter = self.input.graphemes(true).peekable();
22443 let mut offset = 0;
22444 let mut grapheme_len = 0;
22445 if let Some(first_grapheme) = iter.next() {
22446 let is_newline = first_grapheme == "\n";
22447 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22448 offset += first_grapheme.len();
22449 grapheme_len += 1;
22450 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22451 if let Some(grapheme) = iter.peek().copied()
22452 && should_stay_with_preceding_ideograph(grapheme)
22453 {
22454 offset += grapheme.len();
22455 grapheme_len += 1;
22456 }
22457 } else {
22458 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22459 let mut next_word_bound = words.peek().copied();
22460 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22461 next_word_bound = words.next();
22462 }
22463 while let Some(grapheme) = iter.peek().copied() {
22464 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22465 break;
22466 };
22467 if is_grapheme_whitespace(grapheme) != is_whitespace
22468 || (grapheme == "\n") != is_newline
22469 {
22470 break;
22471 };
22472 offset += grapheme.len();
22473 grapheme_len += 1;
22474 iter.next();
22475 }
22476 }
22477 let token = &self.input[..offset];
22478 self.input = &self.input[offset..];
22479 if token == "\n" {
22480 Some(WordBreakToken::Newline)
22481 } else if is_whitespace {
22482 Some(WordBreakToken::InlineWhitespace {
22483 token,
22484 grapheme_len,
22485 })
22486 } else {
22487 Some(WordBreakToken::Word {
22488 token,
22489 grapheme_len,
22490 })
22491 }
22492 } else {
22493 None
22494 }
22495 }
22496}
22497
22498#[test]
22499fn test_word_breaking_tokenizer() {
22500 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22501 ("", &[]),
22502 (" ", &[whitespace(" ", 2)]),
22503 ("Ʒ", &[word("Ʒ", 1)]),
22504 ("Ǽ", &[word("Ǽ", 1)]),
22505 ("⋑", &[word("⋑", 1)]),
22506 ("⋑⋑", &[word("⋑⋑", 2)]),
22507 (
22508 "原理,进而",
22509 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22510 ),
22511 (
22512 "hello world",
22513 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22514 ),
22515 (
22516 "hello, world",
22517 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22518 ),
22519 (
22520 " hello world",
22521 &[
22522 whitespace(" ", 2),
22523 word("hello", 5),
22524 whitespace(" ", 1),
22525 word("world", 5),
22526 ],
22527 ),
22528 (
22529 "这是什么 \n 钢笔",
22530 &[
22531 word("这", 1),
22532 word("是", 1),
22533 word("什", 1),
22534 word("么", 1),
22535 whitespace(" ", 1),
22536 newline(),
22537 whitespace(" ", 1),
22538 word("钢", 1),
22539 word("笔", 1),
22540 ],
22541 ),
22542 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22543 ];
22544
22545 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22546 WordBreakToken::Word {
22547 token,
22548 grapheme_len,
22549 }
22550 }
22551
22552 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22553 WordBreakToken::InlineWhitespace {
22554 token,
22555 grapheme_len,
22556 }
22557 }
22558
22559 fn newline() -> WordBreakToken<'static> {
22560 WordBreakToken::Newline
22561 }
22562
22563 for (input, result) in tests {
22564 assert_eq!(
22565 WordBreakingTokenizer::new(input)
22566 .collect::<Vec<_>>()
22567 .as_slice(),
22568 *result,
22569 );
22570 }
22571}
22572
22573fn wrap_with_prefix(
22574 first_line_prefix: String,
22575 subsequent_lines_prefix: String,
22576 unwrapped_text: String,
22577 wrap_column: usize,
22578 tab_size: NonZeroU32,
22579 preserve_existing_whitespace: bool,
22580) -> String {
22581 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22582 let subsequent_lines_prefix_len =
22583 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22584 let mut wrapped_text = String::new();
22585 let mut current_line = first_line_prefix;
22586 let mut is_first_line = true;
22587
22588 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22589 let mut current_line_len = first_line_prefix_len;
22590 let mut in_whitespace = false;
22591 for token in tokenizer {
22592 let have_preceding_whitespace = in_whitespace;
22593 match token {
22594 WordBreakToken::Word {
22595 token,
22596 grapheme_len,
22597 } => {
22598 in_whitespace = false;
22599 let current_prefix_len = if is_first_line {
22600 first_line_prefix_len
22601 } else {
22602 subsequent_lines_prefix_len
22603 };
22604 if current_line_len + grapheme_len > wrap_column
22605 && current_line_len != current_prefix_len
22606 {
22607 wrapped_text.push_str(current_line.trim_end());
22608 wrapped_text.push('\n');
22609 is_first_line = false;
22610 current_line = subsequent_lines_prefix.clone();
22611 current_line_len = subsequent_lines_prefix_len;
22612 }
22613 current_line.push_str(token);
22614 current_line_len += grapheme_len;
22615 }
22616 WordBreakToken::InlineWhitespace {
22617 mut token,
22618 mut grapheme_len,
22619 } => {
22620 in_whitespace = true;
22621 if have_preceding_whitespace && !preserve_existing_whitespace {
22622 continue;
22623 }
22624 if !preserve_existing_whitespace {
22625 // Keep a single whitespace grapheme as-is
22626 if let Some(first) =
22627 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22628 {
22629 token = first;
22630 } else {
22631 token = " ";
22632 }
22633 grapheme_len = 1;
22634 }
22635 let current_prefix_len = if is_first_line {
22636 first_line_prefix_len
22637 } else {
22638 subsequent_lines_prefix_len
22639 };
22640 if current_line_len + grapheme_len > wrap_column {
22641 wrapped_text.push_str(current_line.trim_end());
22642 wrapped_text.push('\n');
22643 is_first_line = false;
22644 current_line = subsequent_lines_prefix.clone();
22645 current_line_len = subsequent_lines_prefix_len;
22646 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22647 current_line.push_str(token);
22648 current_line_len += grapheme_len;
22649 }
22650 }
22651 WordBreakToken::Newline => {
22652 in_whitespace = true;
22653 let current_prefix_len = if is_first_line {
22654 first_line_prefix_len
22655 } else {
22656 subsequent_lines_prefix_len
22657 };
22658 if preserve_existing_whitespace {
22659 wrapped_text.push_str(current_line.trim_end());
22660 wrapped_text.push('\n');
22661 is_first_line = false;
22662 current_line = subsequent_lines_prefix.clone();
22663 current_line_len = subsequent_lines_prefix_len;
22664 } else if have_preceding_whitespace {
22665 continue;
22666 } else if current_line_len + 1 > wrap_column
22667 && current_line_len != current_prefix_len
22668 {
22669 wrapped_text.push_str(current_line.trim_end());
22670 wrapped_text.push('\n');
22671 is_first_line = false;
22672 current_line = subsequent_lines_prefix.clone();
22673 current_line_len = subsequent_lines_prefix_len;
22674 } else if current_line_len != current_prefix_len {
22675 current_line.push(' ');
22676 current_line_len += 1;
22677 }
22678 }
22679 }
22680 }
22681
22682 if !current_line.is_empty() {
22683 wrapped_text.push_str(¤t_line);
22684 }
22685 wrapped_text
22686}
22687
22688#[test]
22689fn test_wrap_with_prefix() {
22690 assert_eq!(
22691 wrap_with_prefix(
22692 "# ".to_string(),
22693 "# ".to_string(),
22694 "abcdefg".to_string(),
22695 4,
22696 NonZeroU32::new(4).unwrap(),
22697 false,
22698 ),
22699 "# abcdefg"
22700 );
22701 assert_eq!(
22702 wrap_with_prefix(
22703 "".to_string(),
22704 "".to_string(),
22705 "\thello world".to_string(),
22706 8,
22707 NonZeroU32::new(4).unwrap(),
22708 false,
22709 ),
22710 "hello\nworld"
22711 );
22712 assert_eq!(
22713 wrap_with_prefix(
22714 "// ".to_string(),
22715 "// ".to_string(),
22716 "xx \nyy zz aa bb cc".to_string(),
22717 12,
22718 NonZeroU32::new(4).unwrap(),
22719 false,
22720 ),
22721 "// xx yy zz\n// aa bb cc"
22722 );
22723 assert_eq!(
22724 wrap_with_prefix(
22725 String::new(),
22726 String::new(),
22727 "这是什么 \n 钢笔".to_string(),
22728 3,
22729 NonZeroU32::new(4).unwrap(),
22730 false,
22731 ),
22732 "这是什\n么 钢\n笔"
22733 );
22734 assert_eq!(
22735 wrap_with_prefix(
22736 String::new(),
22737 String::new(),
22738 format!("foo{}bar", '\u{2009}'), // thin space
22739 80,
22740 NonZeroU32::new(4).unwrap(),
22741 false,
22742 ),
22743 format!("foo{}bar", '\u{2009}')
22744 );
22745}
22746
22747pub trait CollaborationHub {
22748 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22749 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22750 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22751}
22752
22753impl CollaborationHub for Entity<Project> {
22754 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22755 self.read(cx).collaborators()
22756 }
22757
22758 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22759 self.read(cx).user_store().read(cx).participant_indices()
22760 }
22761
22762 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22763 let this = self.read(cx);
22764 let user_ids = this.collaborators().values().map(|c| c.user_id);
22765 this.user_store().read(cx).participant_names(user_ids, cx)
22766 }
22767}
22768
22769pub trait SemanticsProvider {
22770 fn hover(
22771 &self,
22772 buffer: &Entity<Buffer>,
22773 position: text::Anchor,
22774 cx: &mut App,
22775 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22776
22777 fn inline_values(
22778 &self,
22779 buffer_handle: Entity<Buffer>,
22780 range: Range<text::Anchor>,
22781 cx: &mut App,
22782 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22783
22784 fn applicable_inlay_chunks(
22785 &self,
22786 buffer: &Entity<Buffer>,
22787 ranges: &[Range<text::Anchor>],
22788 cx: &mut App,
22789 ) -> Vec<Range<BufferRow>>;
22790
22791 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22792
22793 fn inlay_hints(
22794 &self,
22795 invalidate: InvalidationStrategy,
22796 buffer: Entity<Buffer>,
22797 ranges: Vec<Range<text::Anchor>>,
22798 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22799 cx: &mut App,
22800 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22801
22802 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22803
22804 fn document_highlights(
22805 &self,
22806 buffer: &Entity<Buffer>,
22807 position: text::Anchor,
22808 cx: &mut App,
22809 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22810
22811 fn definitions(
22812 &self,
22813 buffer: &Entity<Buffer>,
22814 position: text::Anchor,
22815 kind: GotoDefinitionKind,
22816 cx: &mut App,
22817 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22818
22819 fn range_for_rename(
22820 &self,
22821 buffer: &Entity<Buffer>,
22822 position: text::Anchor,
22823 cx: &mut App,
22824 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22825
22826 fn perform_rename(
22827 &self,
22828 buffer: &Entity<Buffer>,
22829 position: text::Anchor,
22830 new_name: String,
22831 cx: &mut App,
22832 ) -> Option<Task<Result<ProjectTransaction>>>;
22833}
22834
22835pub trait CompletionProvider {
22836 fn completions(
22837 &self,
22838 excerpt_id: ExcerptId,
22839 buffer: &Entity<Buffer>,
22840 buffer_position: text::Anchor,
22841 trigger: CompletionContext,
22842 window: &mut Window,
22843 cx: &mut Context<Editor>,
22844 ) -> Task<Result<Vec<CompletionResponse>>>;
22845
22846 fn resolve_completions(
22847 &self,
22848 _buffer: Entity<Buffer>,
22849 _completion_indices: Vec<usize>,
22850 _completions: Rc<RefCell<Box<[Completion]>>>,
22851 _cx: &mut Context<Editor>,
22852 ) -> Task<Result<bool>> {
22853 Task::ready(Ok(false))
22854 }
22855
22856 fn apply_additional_edits_for_completion(
22857 &self,
22858 _buffer: Entity<Buffer>,
22859 _completions: Rc<RefCell<Box<[Completion]>>>,
22860 _completion_index: usize,
22861 _push_to_history: bool,
22862 _cx: &mut Context<Editor>,
22863 ) -> Task<Result<Option<language::Transaction>>> {
22864 Task::ready(Ok(None))
22865 }
22866
22867 fn is_completion_trigger(
22868 &self,
22869 buffer: &Entity<Buffer>,
22870 position: language::Anchor,
22871 text: &str,
22872 trigger_in_words: bool,
22873 menu_is_open: bool,
22874 cx: &mut Context<Editor>,
22875 ) -> bool;
22876
22877 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22878
22879 fn sort_completions(&self) -> bool {
22880 true
22881 }
22882
22883 fn filter_completions(&self) -> bool {
22884 true
22885 }
22886}
22887
22888pub trait CodeActionProvider {
22889 fn id(&self) -> Arc<str>;
22890
22891 fn code_actions(
22892 &self,
22893 buffer: &Entity<Buffer>,
22894 range: Range<text::Anchor>,
22895 window: &mut Window,
22896 cx: &mut App,
22897 ) -> Task<Result<Vec<CodeAction>>>;
22898
22899 fn apply_code_action(
22900 &self,
22901 buffer_handle: Entity<Buffer>,
22902 action: CodeAction,
22903 excerpt_id: ExcerptId,
22904 push_to_history: bool,
22905 window: &mut Window,
22906 cx: &mut App,
22907 ) -> Task<Result<ProjectTransaction>>;
22908}
22909
22910impl CodeActionProvider for Entity<Project> {
22911 fn id(&self) -> Arc<str> {
22912 "project".into()
22913 }
22914
22915 fn code_actions(
22916 &self,
22917 buffer: &Entity<Buffer>,
22918 range: Range<text::Anchor>,
22919 _window: &mut Window,
22920 cx: &mut App,
22921 ) -> Task<Result<Vec<CodeAction>>> {
22922 self.update(cx, |project, cx| {
22923 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22924 let code_actions = project.code_actions(buffer, range, None, cx);
22925 cx.background_spawn(async move {
22926 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22927 Ok(code_lens_actions
22928 .context("code lens fetch")?
22929 .into_iter()
22930 .flatten()
22931 .chain(
22932 code_actions
22933 .context("code action fetch")?
22934 .into_iter()
22935 .flatten(),
22936 )
22937 .collect())
22938 })
22939 })
22940 }
22941
22942 fn apply_code_action(
22943 &self,
22944 buffer_handle: Entity<Buffer>,
22945 action: CodeAction,
22946 _excerpt_id: ExcerptId,
22947 push_to_history: bool,
22948 _window: &mut Window,
22949 cx: &mut App,
22950 ) -> Task<Result<ProjectTransaction>> {
22951 self.update(cx, |project, cx| {
22952 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22953 })
22954 }
22955}
22956
22957fn snippet_completions(
22958 project: &Project,
22959 buffer: &Entity<Buffer>,
22960 buffer_position: text::Anchor,
22961 cx: &mut App,
22962) -> Task<Result<CompletionResponse>> {
22963 let languages = buffer.read(cx).languages_at(buffer_position);
22964 let snippet_store = project.snippets().read(cx);
22965
22966 let scopes: Vec<_> = languages
22967 .iter()
22968 .filter_map(|language| {
22969 let language_name = language.lsp_id();
22970 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22971
22972 if snippets.is_empty() {
22973 None
22974 } else {
22975 Some((language.default_scope(), snippets))
22976 }
22977 })
22978 .collect();
22979
22980 if scopes.is_empty() {
22981 return Task::ready(Ok(CompletionResponse {
22982 completions: vec![],
22983 display_options: CompletionDisplayOptions::default(),
22984 is_incomplete: false,
22985 }));
22986 }
22987
22988 let snapshot = buffer.read(cx).text_snapshot();
22989 let executor = cx.background_executor().clone();
22990
22991 cx.background_spawn(async move {
22992 let mut is_incomplete = false;
22993 let mut completions: Vec<Completion> = Vec::new();
22994 for (scope, snippets) in scopes.into_iter() {
22995 let classifier =
22996 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22997
22998 const MAX_WORD_PREFIX_LEN: usize = 128;
22999 let last_word: String = snapshot
23000 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23001 .take(MAX_WORD_PREFIX_LEN)
23002 .take_while(|c| classifier.is_word(*c))
23003 .collect::<String>()
23004 .chars()
23005 .rev()
23006 .collect();
23007
23008 if last_word.is_empty() {
23009 return Ok(CompletionResponse {
23010 completions: vec![],
23011 display_options: CompletionDisplayOptions::default(),
23012 is_incomplete: true,
23013 });
23014 }
23015
23016 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23017 let to_lsp = |point: &text::Anchor| {
23018 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23019 point_to_lsp(end)
23020 };
23021 let lsp_end = to_lsp(&buffer_position);
23022
23023 let candidates = snippets
23024 .iter()
23025 .enumerate()
23026 .flat_map(|(ix, snippet)| {
23027 snippet
23028 .prefix
23029 .iter()
23030 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23031 })
23032 .collect::<Vec<StringMatchCandidate>>();
23033
23034 const MAX_RESULTS: usize = 100;
23035 let mut matches = fuzzy::match_strings(
23036 &candidates,
23037 &last_word,
23038 last_word.chars().any(|c| c.is_uppercase()),
23039 true,
23040 MAX_RESULTS,
23041 &Default::default(),
23042 executor.clone(),
23043 )
23044 .await;
23045
23046 if matches.len() >= MAX_RESULTS {
23047 is_incomplete = true;
23048 }
23049
23050 // Remove all candidates where the query's start does not match the start of any word in the candidate
23051 if let Some(query_start) = last_word.chars().next() {
23052 matches.retain(|string_match| {
23053 split_words(&string_match.string).any(|word| {
23054 // Check that the first codepoint of the word as lowercase matches the first
23055 // codepoint of the query as lowercase
23056 word.chars()
23057 .flat_map(|codepoint| codepoint.to_lowercase())
23058 .zip(query_start.to_lowercase())
23059 .all(|(word_cp, query_cp)| word_cp == query_cp)
23060 })
23061 });
23062 }
23063
23064 let matched_strings = matches
23065 .into_iter()
23066 .map(|m| m.string)
23067 .collect::<HashSet<_>>();
23068
23069 completions.extend(snippets.iter().filter_map(|snippet| {
23070 let matching_prefix = snippet
23071 .prefix
23072 .iter()
23073 .find(|prefix| matched_strings.contains(*prefix))?;
23074 let start = as_offset - last_word.len();
23075 let start = snapshot.anchor_before(start);
23076 let range = start..buffer_position;
23077 let lsp_start = to_lsp(&start);
23078 let lsp_range = lsp::Range {
23079 start: lsp_start,
23080 end: lsp_end,
23081 };
23082 Some(Completion {
23083 replace_range: range,
23084 new_text: snippet.body.clone(),
23085 source: CompletionSource::Lsp {
23086 insert_range: None,
23087 server_id: LanguageServerId(usize::MAX),
23088 resolved: true,
23089 lsp_completion: Box::new(lsp::CompletionItem {
23090 label: snippet.prefix.first().unwrap().clone(),
23091 kind: Some(CompletionItemKind::SNIPPET),
23092 label_details: snippet.description.as_ref().map(|description| {
23093 lsp::CompletionItemLabelDetails {
23094 detail: Some(description.clone()),
23095 description: None,
23096 }
23097 }),
23098 insert_text_format: Some(InsertTextFormat::SNIPPET),
23099 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23100 lsp::InsertReplaceEdit {
23101 new_text: snippet.body.clone(),
23102 insert: lsp_range,
23103 replace: lsp_range,
23104 },
23105 )),
23106 filter_text: Some(snippet.body.clone()),
23107 sort_text: Some(char::MAX.to_string()),
23108 ..lsp::CompletionItem::default()
23109 }),
23110 lsp_defaults: None,
23111 },
23112 label: CodeLabel::plain(matching_prefix.clone(), None),
23113 icon_path: None,
23114 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23115 single_line: snippet.name.clone().into(),
23116 plain_text: snippet
23117 .description
23118 .clone()
23119 .map(|description| description.into()),
23120 }),
23121 insert_text_mode: None,
23122 confirm: None,
23123 })
23124 }))
23125 }
23126
23127 Ok(CompletionResponse {
23128 completions,
23129 display_options: CompletionDisplayOptions::default(),
23130 is_incomplete,
23131 })
23132 })
23133}
23134
23135impl CompletionProvider for Entity<Project> {
23136 fn completions(
23137 &self,
23138 _excerpt_id: ExcerptId,
23139 buffer: &Entity<Buffer>,
23140 buffer_position: text::Anchor,
23141 options: CompletionContext,
23142 _window: &mut Window,
23143 cx: &mut Context<Editor>,
23144 ) -> Task<Result<Vec<CompletionResponse>>> {
23145 self.update(cx, |project, cx| {
23146 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23147 let project_completions = project.completions(buffer, buffer_position, options, cx);
23148 cx.background_spawn(async move {
23149 let mut responses = project_completions.await?;
23150 let snippets = snippets.await?;
23151 if !snippets.completions.is_empty() {
23152 responses.push(snippets);
23153 }
23154 Ok(responses)
23155 })
23156 })
23157 }
23158
23159 fn resolve_completions(
23160 &self,
23161 buffer: Entity<Buffer>,
23162 completion_indices: Vec<usize>,
23163 completions: Rc<RefCell<Box<[Completion]>>>,
23164 cx: &mut Context<Editor>,
23165 ) -> Task<Result<bool>> {
23166 self.update(cx, |project, cx| {
23167 project.lsp_store().update(cx, |lsp_store, cx| {
23168 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23169 })
23170 })
23171 }
23172
23173 fn apply_additional_edits_for_completion(
23174 &self,
23175 buffer: Entity<Buffer>,
23176 completions: Rc<RefCell<Box<[Completion]>>>,
23177 completion_index: usize,
23178 push_to_history: bool,
23179 cx: &mut Context<Editor>,
23180 ) -> Task<Result<Option<language::Transaction>>> {
23181 self.update(cx, |project, cx| {
23182 project.lsp_store().update(cx, |lsp_store, cx| {
23183 lsp_store.apply_additional_edits_for_completion(
23184 buffer,
23185 completions,
23186 completion_index,
23187 push_to_history,
23188 cx,
23189 )
23190 })
23191 })
23192 }
23193
23194 fn is_completion_trigger(
23195 &self,
23196 buffer: &Entity<Buffer>,
23197 position: language::Anchor,
23198 text: &str,
23199 trigger_in_words: bool,
23200 menu_is_open: bool,
23201 cx: &mut Context<Editor>,
23202 ) -> bool {
23203 let mut chars = text.chars();
23204 let char = if let Some(char) = chars.next() {
23205 char
23206 } else {
23207 return false;
23208 };
23209 if chars.next().is_some() {
23210 return false;
23211 }
23212
23213 let buffer = buffer.read(cx);
23214 let snapshot = buffer.snapshot();
23215 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23216 return false;
23217 }
23218 let classifier = snapshot
23219 .char_classifier_at(position)
23220 .scope_context(Some(CharScopeContext::Completion));
23221 if trigger_in_words && classifier.is_word(char) {
23222 return true;
23223 }
23224
23225 buffer.completion_triggers().contains(text)
23226 }
23227}
23228
23229impl SemanticsProvider for Entity<Project> {
23230 fn hover(
23231 &self,
23232 buffer: &Entity<Buffer>,
23233 position: text::Anchor,
23234 cx: &mut App,
23235 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23236 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23237 }
23238
23239 fn document_highlights(
23240 &self,
23241 buffer: &Entity<Buffer>,
23242 position: text::Anchor,
23243 cx: &mut App,
23244 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23245 Some(self.update(cx, |project, cx| {
23246 project.document_highlights(buffer, position, cx)
23247 }))
23248 }
23249
23250 fn definitions(
23251 &self,
23252 buffer: &Entity<Buffer>,
23253 position: text::Anchor,
23254 kind: GotoDefinitionKind,
23255 cx: &mut App,
23256 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23257 Some(self.update(cx, |project, cx| match kind {
23258 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23259 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23260 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23261 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23262 }))
23263 }
23264
23265 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23266 self.update(cx, |project, cx| {
23267 if project
23268 .active_debug_session(cx)
23269 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23270 {
23271 return true;
23272 }
23273
23274 buffer.update(cx, |buffer, cx| {
23275 project.any_language_server_supports_inlay_hints(buffer, cx)
23276 })
23277 })
23278 }
23279
23280 fn inline_values(
23281 &self,
23282 buffer_handle: Entity<Buffer>,
23283 range: Range<text::Anchor>,
23284 cx: &mut App,
23285 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23286 self.update(cx, |project, cx| {
23287 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23288
23289 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23290 })
23291 }
23292
23293 fn applicable_inlay_chunks(
23294 &self,
23295 buffer: &Entity<Buffer>,
23296 ranges: &[Range<text::Anchor>],
23297 cx: &mut App,
23298 ) -> Vec<Range<BufferRow>> {
23299 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23300 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23301 })
23302 }
23303
23304 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23305 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23306 lsp_store.invalidate_inlay_hints(for_buffers)
23307 });
23308 }
23309
23310 fn inlay_hints(
23311 &self,
23312 invalidate: InvalidationStrategy,
23313 buffer: Entity<Buffer>,
23314 ranges: Vec<Range<text::Anchor>>,
23315 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23316 cx: &mut App,
23317 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23318 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23319 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23320 }))
23321 }
23322
23323 fn range_for_rename(
23324 &self,
23325 buffer: &Entity<Buffer>,
23326 position: text::Anchor,
23327 cx: &mut App,
23328 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23329 Some(self.update(cx, |project, cx| {
23330 let buffer = buffer.clone();
23331 let task = project.prepare_rename(buffer.clone(), position, cx);
23332 cx.spawn(async move |_, cx| {
23333 Ok(match task.await? {
23334 PrepareRenameResponse::Success(range) => Some(range),
23335 PrepareRenameResponse::InvalidPosition => None,
23336 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23337 // Fallback on using TreeSitter info to determine identifier range
23338 buffer.read_with(cx, |buffer, _| {
23339 let snapshot = buffer.snapshot();
23340 let (range, kind) = snapshot.surrounding_word(position, None);
23341 if kind != Some(CharKind::Word) {
23342 return None;
23343 }
23344 Some(
23345 snapshot.anchor_before(range.start)
23346 ..snapshot.anchor_after(range.end),
23347 )
23348 })?
23349 }
23350 })
23351 })
23352 }))
23353 }
23354
23355 fn perform_rename(
23356 &self,
23357 buffer: &Entity<Buffer>,
23358 position: text::Anchor,
23359 new_name: String,
23360 cx: &mut App,
23361 ) -> Option<Task<Result<ProjectTransaction>>> {
23362 Some(self.update(cx, |project, cx| {
23363 project.perform_rename(buffer.clone(), position, new_name, cx)
23364 }))
23365 }
23366}
23367
23368fn consume_contiguous_rows(
23369 contiguous_row_selections: &mut Vec<Selection<Point>>,
23370 selection: &Selection<Point>,
23371 display_map: &DisplaySnapshot,
23372 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23373) -> (MultiBufferRow, MultiBufferRow) {
23374 contiguous_row_selections.push(selection.clone());
23375 let start_row = starting_row(selection, display_map);
23376 let mut end_row = ending_row(selection, display_map);
23377
23378 while let Some(next_selection) = selections.peek() {
23379 if next_selection.start.row <= end_row.0 {
23380 end_row = ending_row(next_selection, display_map);
23381 contiguous_row_selections.push(selections.next().unwrap().clone());
23382 } else {
23383 break;
23384 }
23385 }
23386 (start_row, end_row)
23387}
23388
23389fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23390 if selection.start.column > 0 {
23391 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23392 } else {
23393 MultiBufferRow(selection.start.row)
23394 }
23395}
23396
23397fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23398 if next_selection.end.column > 0 || next_selection.is_empty() {
23399 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23400 } else {
23401 MultiBufferRow(next_selection.end.row)
23402 }
23403}
23404
23405impl EditorSnapshot {
23406 pub fn remote_selections_in_range<'a>(
23407 &'a self,
23408 range: &'a Range<Anchor>,
23409 collaboration_hub: &dyn CollaborationHub,
23410 cx: &'a App,
23411 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23412 let participant_names = collaboration_hub.user_names(cx);
23413 let participant_indices = collaboration_hub.user_participant_indices(cx);
23414 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23415 let collaborators_by_replica_id = collaborators_by_peer_id
23416 .values()
23417 .map(|collaborator| (collaborator.replica_id, collaborator))
23418 .collect::<HashMap<_, _>>();
23419 self.buffer_snapshot()
23420 .selections_in_range(range, false)
23421 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23422 if replica_id == ReplicaId::AGENT {
23423 Some(RemoteSelection {
23424 replica_id,
23425 selection,
23426 cursor_shape,
23427 line_mode,
23428 collaborator_id: CollaboratorId::Agent,
23429 user_name: Some("Agent".into()),
23430 color: cx.theme().players().agent(),
23431 })
23432 } else {
23433 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23434 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23435 let user_name = participant_names.get(&collaborator.user_id).cloned();
23436 Some(RemoteSelection {
23437 replica_id,
23438 selection,
23439 cursor_shape,
23440 line_mode,
23441 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23442 user_name,
23443 color: if let Some(index) = participant_index {
23444 cx.theme().players().color_for_participant(index.0)
23445 } else {
23446 cx.theme().players().absent()
23447 },
23448 })
23449 }
23450 })
23451 }
23452
23453 pub fn hunks_for_ranges(
23454 &self,
23455 ranges: impl IntoIterator<Item = Range<Point>>,
23456 ) -> Vec<MultiBufferDiffHunk> {
23457 let mut hunks = Vec::new();
23458 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23459 HashMap::default();
23460 for query_range in ranges {
23461 let query_rows =
23462 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23463 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23464 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23465 ) {
23466 // Include deleted hunks that are adjacent to the query range, because
23467 // otherwise they would be missed.
23468 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23469 if hunk.status().is_deleted() {
23470 intersects_range |= hunk.row_range.start == query_rows.end;
23471 intersects_range |= hunk.row_range.end == query_rows.start;
23472 }
23473 if intersects_range {
23474 if !processed_buffer_rows
23475 .entry(hunk.buffer_id)
23476 .or_default()
23477 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23478 {
23479 continue;
23480 }
23481 hunks.push(hunk);
23482 }
23483 }
23484 }
23485
23486 hunks
23487 }
23488
23489 fn display_diff_hunks_for_rows<'a>(
23490 &'a self,
23491 display_rows: Range<DisplayRow>,
23492 folded_buffers: &'a HashSet<BufferId>,
23493 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23494 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23495 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23496
23497 self.buffer_snapshot()
23498 .diff_hunks_in_range(buffer_start..buffer_end)
23499 .filter_map(|hunk| {
23500 if folded_buffers.contains(&hunk.buffer_id) {
23501 return None;
23502 }
23503
23504 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23505 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23506
23507 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23508 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23509
23510 let display_hunk = if hunk_display_start.column() != 0 {
23511 DisplayDiffHunk::Folded {
23512 display_row: hunk_display_start.row(),
23513 }
23514 } else {
23515 let mut end_row = hunk_display_end.row();
23516 if hunk_display_end.column() > 0 {
23517 end_row.0 += 1;
23518 }
23519 let is_created_file = hunk.is_created_file();
23520 DisplayDiffHunk::Unfolded {
23521 status: hunk.status(),
23522 diff_base_byte_range: hunk.diff_base_byte_range,
23523 display_row_range: hunk_display_start.row()..end_row,
23524 multi_buffer_range: Anchor::range_in_buffer(
23525 hunk.excerpt_id,
23526 hunk.buffer_id,
23527 hunk.buffer_range,
23528 ),
23529 is_created_file,
23530 }
23531 };
23532
23533 Some(display_hunk)
23534 })
23535 }
23536
23537 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23538 self.display_snapshot
23539 .buffer_snapshot()
23540 .language_at(position)
23541 }
23542
23543 pub fn is_focused(&self) -> bool {
23544 self.is_focused
23545 }
23546
23547 pub fn placeholder_text(&self) -> Option<String> {
23548 self.placeholder_display_snapshot
23549 .as_ref()
23550 .map(|display_map| display_map.text())
23551 }
23552
23553 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23554 self.scroll_anchor.scroll_position(&self.display_snapshot)
23555 }
23556
23557 fn gutter_dimensions(
23558 &self,
23559 font_id: FontId,
23560 font_size: Pixels,
23561 max_line_number_width: Pixels,
23562 cx: &App,
23563 ) -> Option<GutterDimensions> {
23564 if !self.show_gutter {
23565 return None;
23566 }
23567
23568 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23569 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23570
23571 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23572 matches!(
23573 ProjectSettings::get_global(cx).git.git_gutter,
23574 GitGutterSetting::TrackedFiles
23575 )
23576 });
23577 let gutter_settings = EditorSettings::get_global(cx).gutter;
23578 let show_line_numbers = self
23579 .show_line_numbers
23580 .unwrap_or(gutter_settings.line_numbers);
23581 let line_gutter_width = if show_line_numbers {
23582 // Avoid flicker-like gutter resizes when the line number gains another digit by
23583 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23584 let min_width_for_number_on_gutter =
23585 ch_advance * gutter_settings.min_line_number_digits as f32;
23586 max_line_number_width.max(min_width_for_number_on_gutter)
23587 } else {
23588 0.0.into()
23589 };
23590
23591 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23592 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23593
23594 let git_blame_entries_width =
23595 self.git_blame_gutter_max_author_length
23596 .map(|max_author_length| {
23597 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23598 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23599
23600 /// The number of characters to dedicate to gaps and margins.
23601 const SPACING_WIDTH: usize = 4;
23602
23603 let max_char_count = max_author_length.min(renderer.max_author_length())
23604 + ::git::SHORT_SHA_LENGTH
23605 + MAX_RELATIVE_TIMESTAMP.len()
23606 + SPACING_WIDTH;
23607
23608 ch_advance * max_char_count
23609 });
23610
23611 let is_singleton = self.buffer_snapshot().is_singleton();
23612
23613 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23614 left_padding += if !is_singleton {
23615 ch_width * 4.0
23616 } else if show_runnables || show_breakpoints {
23617 ch_width * 3.0
23618 } else if show_git_gutter && show_line_numbers {
23619 ch_width * 2.0
23620 } else if show_git_gutter || show_line_numbers {
23621 ch_width
23622 } else {
23623 px(0.)
23624 };
23625
23626 let shows_folds = is_singleton && gutter_settings.folds;
23627
23628 let right_padding = if shows_folds && show_line_numbers {
23629 ch_width * 4.0
23630 } else if shows_folds || (!is_singleton && show_line_numbers) {
23631 ch_width * 3.0
23632 } else if show_line_numbers {
23633 ch_width
23634 } else {
23635 px(0.)
23636 };
23637
23638 Some(GutterDimensions {
23639 left_padding,
23640 right_padding,
23641 width: line_gutter_width + left_padding + right_padding,
23642 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23643 git_blame_entries_width,
23644 })
23645 }
23646
23647 pub fn render_crease_toggle(
23648 &self,
23649 buffer_row: MultiBufferRow,
23650 row_contains_cursor: bool,
23651 editor: Entity<Editor>,
23652 window: &mut Window,
23653 cx: &mut App,
23654 ) -> Option<AnyElement> {
23655 let folded = self.is_line_folded(buffer_row);
23656 let mut is_foldable = false;
23657
23658 if let Some(crease) = self
23659 .crease_snapshot
23660 .query_row(buffer_row, self.buffer_snapshot())
23661 {
23662 is_foldable = true;
23663 match crease {
23664 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23665 if let Some(render_toggle) = render_toggle {
23666 let toggle_callback =
23667 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23668 if folded {
23669 editor.update(cx, |editor, cx| {
23670 editor.fold_at(buffer_row, window, cx)
23671 });
23672 } else {
23673 editor.update(cx, |editor, cx| {
23674 editor.unfold_at(buffer_row, window, cx)
23675 });
23676 }
23677 });
23678 return Some((render_toggle)(
23679 buffer_row,
23680 folded,
23681 toggle_callback,
23682 window,
23683 cx,
23684 ));
23685 }
23686 }
23687 }
23688 }
23689
23690 is_foldable |= self.starts_indent(buffer_row);
23691
23692 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23693 Some(
23694 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23695 .toggle_state(folded)
23696 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23697 if folded {
23698 this.unfold_at(buffer_row, window, cx);
23699 } else {
23700 this.fold_at(buffer_row, window, cx);
23701 }
23702 }))
23703 .into_any_element(),
23704 )
23705 } else {
23706 None
23707 }
23708 }
23709
23710 pub fn render_crease_trailer(
23711 &self,
23712 buffer_row: MultiBufferRow,
23713 window: &mut Window,
23714 cx: &mut App,
23715 ) -> Option<AnyElement> {
23716 let folded = self.is_line_folded(buffer_row);
23717 if let Crease::Inline { render_trailer, .. } = self
23718 .crease_snapshot
23719 .query_row(buffer_row, self.buffer_snapshot())?
23720 {
23721 let render_trailer = render_trailer.as_ref()?;
23722 Some(render_trailer(buffer_row, folded, window, cx))
23723 } else {
23724 None
23725 }
23726 }
23727}
23728
23729impl Deref for EditorSnapshot {
23730 type Target = DisplaySnapshot;
23731
23732 fn deref(&self) -> &Self::Target {
23733 &self.display_snapshot
23734 }
23735}
23736
23737#[derive(Clone, Debug, PartialEq, Eq)]
23738pub enum EditorEvent {
23739 InputIgnored {
23740 text: Arc<str>,
23741 },
23742 InputHandled {
23743 utf16_range_to_replace: Option<Range<isize>>,
23744 text: Arc<str>,
23745 },
23746 ExcerptsAdded {
23747 buffer: Entity<Buffer>,
23748 predecessor: ExcerptId,
23749 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23750 },
23751 ExcerptsRemoved {
23752 ids: Vec<ExcerptId>,
23753 removed_buffer_ids: Vec<BufferId>,
23754 },
23755 BufferFoldToggled {
23756 ids: Vec<ExcerptId>,
23757 folded: bool,
23758 },
23759 ExcerptsEdited {
23760 ids: Vec<ExcerptId>,
23761 },
23762 ExcerptsExpanded {
23763 ids: Vec<ExcerptId>,
23764 },
23765 BufferEdited,
23766 Edited {
23767 transaction_id: clock::Lamport,
23768 },
23769 Reparsed(BufferId),
23770 Focused,
23771 FocusedIn,
23772 Blurred,
23773 DirtyChanged,
23774 Saved,
23775 TitleChanged,
23776 SelectionsChanged {
23777 local: bool,
23778 },
23779 ScrollPositionChanged {
23780 local: bool,
23781 autoscroll: bool,
23782 },
23783 TransactionUndone {
23784 transaction_id: clock::Lamport,
23785 },
23786 TransactionBegun {
23787 transaction_id: clock::Lamport,
23788 },
23789 CursorShapeChanged,
23790 BreadcrumbsChanged,
23791 PushedToNavHistory {
23792 anchor: Anchor,
23793 is_deactivate: bool,
23794 },
23795}
23796
23797impl EventEmitter<EditorEvent> for Editor {}
23798
23799impl Focusable for Editor {
23800 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23801 self.focus_handle.clone()
23802 }
23803}
23804
23805impl Render for Editor {
23806 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23807 let settings = ThemeSettings::get_global(cx);
23808
23809 let mut text_style = match self.mode {
23810 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23811 color: cx.theme().colors().editor_foreground,
23812 font_family: settings.ui_font.family.clone(),
23813 font_features: settings.ui_font.features.clone(),
23814 font_fallbacks: settings.ui_font.fallbacks.clone(),
23815 font_size: rems(0.875).into(),
23816 font_weight: settings.ui_font.weight,
23817 line_height: relative(settings.buffer_line_height.value()),
23818 ..Default::default()
23819 },
23820 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23821 color: cx.theme().colors().editor_foreground,
23822 font_family: settings.buffer_font.family.clone(),
23823 font_features: settings.buffer_font.features.clone(),
23824 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23825 font_size: settings.buffer_font_size(cx).into(),
23826 font_weight: settings.buffer_font.weight,
23827 line_height: relative(settings.buffer_line_height.value()),
23828 ..Default::default()
23829 },
23830 };
23831 if let Some(text_style_refinement) = &self.text_style_refinement {
23832 text_style.refine(text_style_refinement)
23833 }
23834
23835 let background = match self.mode {
23836 EditorMode::SingleLine => cx.theme().system().transparent,
23837 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23838 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23839 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23840 };
23841
23842 EditorElement::new(
23843 &cx.entity(),
23844 EditorStyle {
23845 background,
23846 border: cx.theme().colors().border,
23847 local_player: cx.theme().players().local(),
23848 text: text_style,
23849 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23850 syntax: cx.theme().syntax().clone(),
23851 status: cx.theme().status().clone(),
23852 inlay_hints_style: make_inlay_hints_style(cx),
23853 edit_prediction_styles: make_suggestion_styles(cx),
23854 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23855 show_underlines: self.diagnostics_enabled(),
23856 },
23857 )
23858 }
23859}
23860
23861impl EntityInputHandler for Editor {
23862 fn text_for_range(
23863 &mut self,
23864 range_utf16: Range<usize>,
23865 adjusted_range: &mut Option<Range<usize>>,
23866 _: &mut Window,
23867 cx: &mut Context<Self>,
23868 ) -> Option<String> {
23869 let snapshot = self.buffer.read(cx).read(cx);
23870 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23871 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23872 if (start.0..end.0) != range_utf16 {
23873 adjusted_range.replace(start.0..end.0);
23874 }
23875 Some(snapshot.text_for_range(start..end).collect())
23876 }
23877
23878 fn selected_text_range(
23879 &mut self,
23880 ignore_disabled_input: bool,
23881 _: &mut Window,
23882 cx: &mut Context<Self>,
23883 ) -> Option<UTF16Selection> {
23884 // Prevent the IME menu from appearing when holding down an alphabetic key
23885 // while input is disabled.
23886 if !ignore_disabled_input && !self.input_enabled {
23887 return None;
23888 }
23889
23890 let selection = self
23891 .selections
23892 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23893 let range = selection.range();
23894
23895 Some(UTF16Selection {
23896 range: range.start.0..range.end.0,
23897 reversed: selection.reversed,
23898 })
23899 }
23900
23901 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23902 let snapshot = self.buffer.read(cx).read(cx);
23903 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23904 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23905 }
23906
23907 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23908 self.clear_highlights::<InputComposition>(cx);
23909 self.ime_transaction.take();
23910 }
23911
23912 fn replace_text_in_range(
23913 &mut self,
23914 range_utf16: Option<Range<usize>>,
23915 text: &str,
23916 window: &mut Window,
23917 cx: &mut Context<Self>,
23918 ) {
23919 if !self.input_enabled {
23920 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23921 return;
23922 }
23923
23924 self.transact(window, cx, |this, window, cx| {
23925 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23926 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23927 Some(this.selection_replacement_ranges(range_utf16, cx))
23928 } else {
23929 this.marked_text_ranges(cx)
23930 };
23931
23932 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23933 let newest_selection_id = this.selections.newest_anchor().id;
23934 this.selections
23935 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23936 .iter()
23937 .zip(ranges_to_replace.iter())
23938 .find_map(|(selection, range)| {
23939 if selection.id == newest_selection_id {
23940 Some(
23941 (range.start.0 as isize - selection.head().0 as isize)
23942 ..(range.end.0 as isize - selection.head().0 as isize),
23943 )
23944 } else {
23945 None
23946 }
23947 })
23948 });
23949
23950 cx.emit(EditorEvent::InputHandled {
23951 utf16_range_to_replace: range_to_replace,
23952 text: text.into(),
23953 });
23954
23955 if let Some(new_selected_ranges) = new_selected_ranges {
23956 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23957 selections.select_ranges(new_selected_ranges)
23958 });
23959 this.backspace(&Default::default(), window, cx);
23960 }
23961
23962 this.handle_input(text, window, cx);
23963 });
23964
23965 if let Some(transaction) = self.ime_transaction {
23966 self.buffer.update(cx, |buffer, cx| {
23967 buffer.group_until_transaction(transaction, cx);
23968 });
23969 }
23970
23971 self.unmark_text(window, cx);
23972 }
23973
23974 fn replace_and_mark_text_in_range(
23975 &mut self,
23976 range_utf16: Option<Range<usize>>,
23977 text: &str,
23978 new_selected_range_utf16: Option<Range<usize>>,
23979 window: &mut Window,
23980 cx: &mut Context<Self>,
23981 ) {
23982 if !self.input_enabled {
23983 return;
23984 }
23985
23986 let transaction = self.transact(window, cx, |this, window, cx| {
23987 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23988 let snapshot = this.buffer.read(cx).read(cx);
23989 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23990 for marked_range in &mut marked_ranges {
23991 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23992 marked_range.start.0 += relative_range_utf16.start;
23993 marked_range.start =
23994 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23995 marked_range.end =
23996 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23997 }
23998 }
23999 Some(marked_ranges)
24000 } else if let Some(range_utf16) = range_utf16 {
24001 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24002 Some(this.selection_replacement_ranges(range_utf16, cx))
24003 } else {
24004 None
24005 };
24006
24007 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24008 let newest_selection_id = this.selections.newest_anchor().id;
24009 this.selections
24010 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24011 .iter()
24012 .zip(ranges_to_replace.iter())
24013 .find_map(|(selection, range)| {
24014 if selection.id == newest_selection_id {
24015 Some(
24016 (range.start.0 as isize - selection.head().0 as isize)
24017 ..(range.end.0 as isize - selection.head().0 as isize),
24018 )
24019 } else {
24020 None
24021 }
24022 })
24023 });
24024
24025 cx.emit(EditorEvent::InputHandled {
24026 utf16_range_to_replace: range_to_replace,
24027 text: text.into(),
24028 });
24029
24030 if let Some(ranges) = ranges_to_replace {
24031 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24032 s.select_ranges(ranges)
24033 });
24034 }
24035
24036 let marked_ranges = {
24037 let snapshot = this.buffer.read(cx).read(cx);
24038 this.selections
24039 .disjoint_anchors_arc()
24040 .iter()
24041 .map(|selection| {
24042 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24043 })
24044 .collect::<Vec<_>>()
24045 };
24046
24047 if text.is_empty() {
24048 this.unmark_text(window, cx);
24049 } else {
24050 this.highlight_text::<InputComposition>(
24051 marked_ranges.clone(),
24052 HighlightStyle {
24053 underline: Some(UnderlineStyle {
24054 thickness: px(1.),
24055 color: None,
24056 wavy: false,
24057 }),
24058 ..Default::default()
24059 },
24060 cx,
24061 );
24062 }
24063
24064 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24065 let use_autoclose = this.use_autoclose;
24066 let use_auto_surround = this.use_auto_surround;
24067 this.set_use_autoclose(false);
24068 this.set_use_auto_surround(false);
24069 this.handle_input(text, window, cx);
24070 this.set_use_autoclose(use_autoclose);
24071 this.set_use_auto_surround(use_auto_surround);
24072
24073 if let Some(new_selected_range) = new_selected_range_utf16 {
24074 let snapshot = this.buffer.read(cx).read(cx);
24075 let new_selected_ranges = marked_ranges
24076 .into_iter()
24077 .map(|marked_range| {
24078 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24079 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24080 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24081 snapshot.clip_offset_utf16(new_start, Bias::Left)
24082 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24083 })
24084 .collect::<Vec<_>>();
24085
24086 drop(snapshot);
24087 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24088 selections.select_ranges(new_selected_ranges)
24089 });
24090 }
24091 });
24092
24093 self.ime_transaction = self.ime_transaction.or(transaction);
24094 if let Some(transaction) = self.ime_transaction {
24095 self.buffer.update(cx, |buffer, cx| {
24096 buffer.group_until_transaction(transaction, cx);
24097 });
24098 }
24099
24100 if self.text_highlights::<InputComposition>(cx).is_none() {
24101 self.ime_transaction.take();
24102 }
24103 }
24104
24105 fn bounds_for_range(
24106 &mut self,
24107 range_utf16: Range<usize>,
24108 element_bounds: gpui::Bounds<Pixels>,
24109 window: &mut Window,
24110 cx: &mut Context<Self>,
24111 ) -> Option<gpui::Bounds<Pixels>> {
24112 let text_layout_details = self.text_layout_details(window);
24113 let CharacterDimensions {
24114 em_width,
24115 em_advance,
24116 line_height,
24117 } = self.character_dimensions(window);
24118
24119 let snapshot = self.snapshot(window, cx);
24120 let scroll_position = snapshot.scroll_position();
24121 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24122
24123 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24124 let x = Pixels::from(
24125 ScrollOffset::from(
24126 snapshot.x_for_display_point(start, &text_layout_details)
24127 + self.gutter_dimensions.full_width(),
24128 ) - scroll_left,
24129 );
24130 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24131
24132 Some(Bounds {
24133 origin: element_bounds.origin + point(x, y),
24134 size: size(em_width, line_height),
24135 })
24136 }
24137
24138 fn character_index_for_point(
24139 &mut self,
24140 point: gpui::Point<Pixels>,
24141 _window: &mut Window,
24142 _cx: &mut Context<Self>,
24143 ) -> Option<usize> {
24144 let position_map = self.last_position_map.as_ref()?;
24145 if !position_map.text_hitbox.contains(&point) {
24146 return None;
24147 }
24148 let display_point = position_map.point_for_position(point).previous_valid;
24149 let anchor = position_map
24150 .snapshot
24151 .display_point_to_anchor(display_point, Bias::Left);
24152 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24153 Some(utf16_offset.0)
24154 }
24155}
24156
24157trait SelectionExt {
24158 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24159 fn spanned_rows(
24160 &self,
24161 include_end_if_at_line_start: bool,
24162 map: &DisplaySnapshot,
24163 ) -> Range<MultiBufferRow>;
24164}
24165
24166impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24167 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24168 let start = self
24169 .start
24170 .to_point(map.buffer_snapshot())
24171 .to_display_point(map);
24172 let end = self
24173 .end
24174 .to_point(map.buffer_snapshot())
24175 .to_display_point(map);
24176 if self.reversed {
24177 end..start
24178 } else {
24179 start..end
24180 }
24181 }
24182
24183 fn spanned_rows(
24184 &self,
24185 include_end_if_at_line_start: bool,
24186 map: &DisplaySnapshot,
24187 ) -> Range<MultiBufferRow> {
24188 let start = self.start.to_point(map.buffer_snapshot());
24189 let mut end = self.end.to_point(map.buffer_snapshot());
24190 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24191 end.row -= 1;
24192 }
24193
24194 let buffer_start = map.prev_line_boundary(start).0;
24195 let buffer_end = map.next_line_boundary(end).0;
24196 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24197 }
24198}
24199
24200impl<T: InvalidationRegion> InvalidationStack<T> {
24201 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24202 where
24203 S: Clone + ToOffset,
24204 {
24205 while let Some(region) = self.last() {
24206 let all_selections_inside_invalidation_ranges =
24207 if selections.len() == region.ranges().len() {
24208 selections
24209 .iter()
24210 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24211 .all(|(selection, invalidation_range)| {
24212 let head = selection.head().to_offset(buffer);
24213 invalidation_range.start <= head && invalidation_range.end >= head
24214 })
24215 } else {
24216 false
24217 };
24218
24219 if all_selections_inside_invalidation_ranges {
24220 break;
24221 } else {
24222 self.pop();
24223 }
24224 }
24225 }
24226}
24227
24228impl<T> Default for InvalidationStack<T> {
24229 fn default() -> Self {
24230 Self(Default::default())
24231 }
24232}
24233
24234impl<T> Deref for InvalidationStack<T> {
24235 type Target = Vec<T>;
24236
24237 fn deref(&self) -> &Self::Target {
24238 &self.0
24239 }
24240}
24241
24242impl<T> DerefMut for InvalidationStack<T> {
24243 fn deref_mut(&mut self) -> &mut Self::Target {
24244 &mut self.0
24245 }
24246}
24247
24248impl InvalidationRegion for SnippetState {
24249 fn ranges(&self) -> &[Range<Anchor>] {
24250 &self.ranges[self.active_index]
24251 }
24252}
24253
24254fn edit_prediction_edit_text(
24255 current_snapshot: &BufferSnapshot,
24256 edits: &[(Range<Anchor>, String)],
24257 edit_preview: &EditPreview,
24258 include_deletions: bool,
24259 cx: &App,
24260) -> HighlightedText {
24261 let edits = edits
24262 .iter()
24263 .map(|(anchor, text)| {
24264 (
24265 anchor.start.text_anchor..anchor.end.text_anchor,
24266 text.clone(),
24267 )
24268 })
24269 .collect::<Vec<_>>();
24270
24271 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24272}
24273
24274fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24275 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24276 // Just show the raw edit text with basic styling
24277 let mut text = String::new();
24278 let mut highlights = Vec::new();
24279
24280 let insertion_highlight_style = HighlightStyle {
24281 color: Some(cx.theme().colors().text),
24282 ..Default::default()
24283 };
24284
24285 for (_, edit_text) in edits {
24286 let start_offset = text.len();
24287 text.push_str(edit_text);
24288 let end_offset = text.len();
24289
24290 if start_offset < end_offset {
24291 highlights.push((start_offset..end_offset, insertion_highlight_style));
24292 }
24293 }
24294
24295 HighlightedText {
24296 text: text.into(),
24297 highlights,
24298 }
24299}
24300
24301pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24302 match severity {
24303 lsp::DiagnosticSeverity::ERROR => colors.error,
24304 lsp::DiagnosticSeverity::WARNING => colors.warning,
24305 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24306 lsp::DiagnosticSeverity::HINT => colors.info,
24307 _ => colors.ignored,
24308 }
24309}
24310
24311pub fn styled_runs_for_code_label<'a>(
24312 label: &'a CodeLabel,
24313 syntax_theme: &'a theme::SyntaxTheme,
24314) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24315 let fade_out = HighlightStyle {
24316 fade_out: Some(0.35),
24317 ..Default::default()
24318 };
24319
24320 let mut prev_end = label.filter_range.end;
24321 label
24322 .runs
24323 .iter()
24324 .enumerate()
24325 .flat_map(move |(ix, (range, highlight_id))| {
24326 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24327 style
24328 } else {
24329 return Default::default();
24330 };
24331 let muted_style = style.highlight(fade_out);
24332
24333 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24334 if range.start >= label.filter_range.end {
24335 if range.start > prev_end {
24336 runs.push((prev_end..range.start, fade_out));
24337 }
24338 runs.push((range.clone(), muted_style));
24339 } else if range.end <= label.filter_range.end {
24340 runs.push((range.clone(), style));
24341 } else {
24342 runs.push((range.start..label.filter_range.end, style));
24343 runs.push((label.filter_range.end..range.end, muted_style));
24344 }
24345 prev_end = cmp::max(prev_end, range.end);
24346
24347 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24348 runs.push((prev_end..label.text.len(), fade_out));
24349 }
24350
24351 runs
24352 })
24353}
24354
24355pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24356 let mut prev_index = 0;
24357 let mut prev_codepoint: Option<char> = None;
24358 text.char_indices()
24359 .chain([(text.len(), '\0')])
24360 .filter_map(move |(index, codepoint)| {
24361 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24362 let is_boundary = index == text.len()
24363 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24364 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24365 if is_boundary {
24366 let chunk = &text[prev_index..index];
24367 prev_index = index;
24368 Some(chunk)
24369 } else {
24370 None
24371 }
24372 })
24373}
24374
24375pub trait RangeToAnchorExt: Sized {
24376 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24377
24378 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24379 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24380 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24381 }
24382}
24383
24384impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24385 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24386 let start_offset = self.start.to_offset(snapshot);
24387 let end_offset = self.end.to_offset(snapshot);
24388 if start_offset == end_offset {
24389 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24390 } else {
24391 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24392 }
24393 }
24394}
24395
24396pub trait RowExt {
24397 fn as_f64(&self) -> f64;
24398
24399 fn next_row(&self) -> Self;
24400
24401 fn previous_row(&self) -> Self;
24402
24403 fn minus(&self, other: Self) -> u32;
24404}
24405
24406impl RowExt for DisplayRow {
24407 fn as_f64(&self) -> f64 {
24408 self.0 as _
24409 }
24410
24411 fn next_row(&self) -> Self {
24412 Self(self.0 + 1)
24413 }
24414
24415 fn previous_row(&self) -> Self {
24416 Self(self.0.saturating_sub(1))
24417 }
24418
24419 fn minus(&self, other: Self) -> u32 {
24420 self.0 - other.0
24421 }
24422}
24423
24424impl RowExt for MultiBufferRow {
24425 fn as_f64(&self) -> f64 {
24426 self.0 as _
24427 }
24428
24429 fn next_row(&self) -> Self {
24430 Self(self.0 + 1)
24431 }
24432
24433 fn previous_row(&self) -> Self {
24434 Self(self.0.saturating_sub(1))
24435 }
24436
24437 fn minus(&self, other: Self) -> u32 {
24438 self.0 - other.0
24439 }
24440}
24441
24442trait RowRangeExt {
24443 type Row;
24444
24445 fn len(&self) -> usize;
24446
24447 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24448}
24449
24450impl RowRangeExt for Range<MultiBufferRow> {
24451 type Row = MultiBufferRow;
24452
24453 fn len(&self) -> usize {
24454 (self.end.0 - self.start.0) as usize
24455 }
24456
24457 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24458 (self.start.0..self.end.0).map(MultiBufferRow)
24459 }
24460}
24461
24462impl RowRangeExt for Range<DisplayRow> {
24463 type Row = DisplayRow;
24464
24465 fn len(&self) -> usize {
24466 (self.end.0 - self.start.0) as usize
24467 }
24468
24469 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24470 (self.start.0..self.end.0).map(DisplayRow)
24471 }
24472}
24473
24474/// If select range has more than one line, we
24475/// just point the cursor to range.start.
24476fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24477 if range.start.row == range.end.row {
24478 range
24479 } else {
24480 range.start..range.start
24481 }
24482}
24483pub struct KillRing(ClipboardItem);
24484impl Global for KillRing {}
24485
24486const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24487
24488enum BreakpointPromptEditAction {
24489 Log,
24490 Condition,
24491 HitCondition,
24492}
24493
24494struct BreakpointPromptEditor {
24495 pub(crate) prompt: Entity<Editor>,
24496 editor: WeakEntity<Editor>,
24497 breakpoint_anchor: Anchor,
24498 breakpoint: Breakpoint,
24499 edit_action: BreakpointPromptEditAction,
24500 block_ids: HashSet<CustomBlockId>,
24501 editor_margins: Arc<Mutex<EditorMargins>>,
24502 _subscriptions: Vec<Subscription>,
24503}
24504
24505impl BreakpointPromptEditor {
24506 const MAX_LINES: u8 = 4;
24507
24508 fn new(
24509 editor: WeakEntity<Editor>,
24510 breakpoint_anchor: Anchor,
24511 breakpoint: Breakpoint,
24512 edit_action: BreakpointPromptEditAction,
24513 window: &mut Window,
24514 cx: &mut Context<Self>,
24515 ) -> Self {
24516 let base_text = match edit_action {
24517 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24518 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24519 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24520 }
24521 .map(|msg| msg.to_string())
24522 .unwrap_or_default();
24523
24524 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24525 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24526
24527 let prompt = cx.new(|cx| {
24528 let mut prompt = Editor::new(
24529 EditorMode::AutoHeight {
24530 min_lines: 1,
24531 max_lines: Some(Self::MAX_LINES as usize),
24532 },
24533 buffer,
24534 None,
24535 window,
24536 cx,
24537 );
24538 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24539 prompt.set_show_cursor_when_unfocused(false, cx);
24540 prompt.set_placeholder_text(
24541 match edit_action {
24542 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24543 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24544 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24545 },
24546 window,
24547 cx,
24548 );
24549
24550 prompt
24551 });
24552
24553 Self {
24554 prompt,
24555 editor,
24556 breakpoint_anchor,
24557 breakpoint,
24558 edit_action,
24559 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24560 block_ids: Default::default(),
24561 _subscriptions: vec![],
24562 }
24563 }
24564
24565 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24566 self.block_ids.extend(block_ids)
24567 }
24568
24569 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24570 if let Some(editor) = self.editor.upgrade() {
24571 let message = self
24572 .prompt
24573 .read(cx)
24574 .buffer
24575 .read(cx)
24576 .as_singleton()
24577 .expect("A multi buffer in breakpoint prompt isn't possible")
24578 .read(cx)
24579 .as_rope()
24580 .to_string();
24581
24582 editor.update(cx, |editor, cx| {
24583 editor.edit_breakpoint_at_anchor(
24584 self.breakpoint_anchor,
24585 self.breakpoint.clone(),
24586 match self.edit_action {
24587 BreakpointPromptEditAction::Log => {
24588 BreakpointEditAction::EditLogMessage(message.into())
24589 }
24590 BreakpointPromptEditAction::Condition => {
24591 BreakpointEditAction::EditCondition(message.into())
24592 }
24593 BreakpointPromptEditAction::HitCondition => {
24594 BreakpointEditAction::EditHitCondition(message.into())
24595 }
24596 },
24597 cx,
24598 );
24599
24600 editor.remove_blocks(self.block_ids.clone(), None, cx);
24601 cx.focus_self(window);
24602 });
24603 }
24604 }
24605
24606 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24607 self.editor
24608 .update(cx, |editor, cx| {
24609 editor.remove_blocks(self.block_ids.clone(), None, cx);
24610 window.focus(&editor.focus_handle);
24611 })
24612 .log_err();
24613 }
24614
24615 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24616 let settings = ThemeSettings::get_global(cx);
24617 let text_style = TextStyle {
24618 color: if self.prompt.read(cx).read_only(cx) {
24619 cx.theme().colors().text_disabled
24620 } else {
24621 cx.theme().colors().text
24622 },
24623 font_family: settings.buffer_font.family.clone(),
24624 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24625 font_size: settings.buffer_font_size(cx).into(),
24626 font_weight: settings.buffer_font.weight,
24627 line_height: relative(settings.buffer_line_height.value()),
24628 ..Default::default()
24629 };
24630 EditorElement::new(
24631 &self.prompt,
24632 EditorStyle {
24633 background: cx.theme().colors().editor_background,
24634 local_player: cx.theme().players().local(),
24635 text: text_style,
24636 ..Default::default()
24637 },
24638 )
24639 }
24640}
24641
24642impl Render for BreakpointPromptEditor {
24643 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24644 let editor_margins = *self.editor_margins.lock();
24645 let gutter_dimensions = editor_margins.gutter;
24646 h_flex()
24647 .key_context("Editor")
24648 .bg(cx.theme().colors().editor_background)
24649 .border_y_1()
24650 .border_color(cx.theme().status().info_border)
24651 .size_full()
24652 .py(window.line_height() / 2.5)
24653 .on_action(cx.listener(Self::confirm))
24654 .on_action(cx.listener(Self::cancel))
24655 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24656 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24657 }
24658}
24659
24660impl Focusable for BreakpointPromptEditor {
24661 fn focus_handle(&self, cx: &App) -> FocusHandle {
24662 self.prompt.focus_handle(cx)
24663 }
24664}
24665
24666fn all_edits_insertions_or_deletions(
24667 edits: &Vec<(Range<Anchor>, String)>,
24668 snapshot: &MultiBufferSnapshot,
24669) -> bool {
24670 let mut all_insertions = true;
24671 let mut all_deletions = true;
24672
24673 for (range, new_text) in edits.iter() {
24674 let range_is_empty = range.to_offset(snapshot).is_empty();
24675 let text_is_empty = new_text.is_empty();
24676
24677 if range_is_empty != text_is_empty {
24678 if range_is_empty {
24679 all_deletions = false;
24680 } else {
24681 all_insertions = false;
24682 }
24683 } else {
24684 return false;
24685 }
24686
24687 if !all_insertions && !all_deletions {
24688 return false;
24689 }
24690 }
24691 all_insertions || all_deletions
24692}
24693
24694struct MissingEditPredictionKeybindingTooltip;
24695
24696impl Render for MissingEditPredictionKeybindingTooltip {
24697 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24698 ui::tooltip_container(cx, |container, cx| {
24699 container
24700 .flex_shrink_0()
24701 .max_w_80()
24702 .min_h(rems_from_px(124.))
24703 .justify_between()
24704 .child(
24705 v_flex()
24706 .flex_1()
24707 .text_ui_sm(cx)
24708 .child(Label::new("Conflict with Accept Keybinding"))
24709 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24710 )
24711 .child(
24712 h_flex()
24713 .pb_1()
24714 .gap_1()
24715 .items_end()
24716 .w_full()
24717 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24718 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24719 }))
24720 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24721 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24722 })),
24723 )
24724 })
24725 }
24726}
24727
24728#[derive(Debug, Clone, Copy, PartialEq)]
24729pub struct LineHighlight {
24730 pub background: Background,
24731 pub border: Option<gpui::Hsla>,
24732 pub include_gutter: bool,
24733 pub type_id: Option<TypeId>,
24734}
24735
24736struct LineManipulationResult {
24737 pub new_text: String,
24738 pub line_count_before: usize,
24739 pub line_count_after: usize,
24740}
24741
24742fn render_diff_hunk_controls(
24743 row: u32,
24744 status: &DiffHunkStatus,
24745 hunk_range: Range<Anchor>,
24746 is_created_file: bool,
24747 line_height: Pixels,
24748 editor: &Entity<Editor>,
24749 _window: &mut Window,
24750 cx: &mut App,
24751) -> AnyElement {
24752 h_flex()
24753 .h(line_height)
24754 .mr_1()
24755 .gap_1()
24756 .px_0p5()
24757 .pb_1()
24758 .border_x_1()
24759 .border_b_1()
24760 .border_color(cx.theme().colors().border_variant)
24761 .rounded_b_lg()
24762 .bg(cx.theme().colors().editor_background)
24763 .gap_1()
24764 .block_mouse_except_scroll()
24765 .shadow_md()
24766 .child(if status.has_secondary_hunk() {
24767 Button::new(("stage", row as u64), "Stage")
24768 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24769 .tooltip({
24770 let focus_handle = editor.focus_handle(cx);
24771 move |_window, cx| {
24772 Tooltip::for_action_in(
24773 "Stage Hunk",
24774 &::git::ToggleStaged,
24775 &focus_handle,
24776 cx,
24777 )
24778 }
24779 })
24780 .on_click({
24781 let editor = editor.clone();
24782 move |_event, _window, cx| {
24783 editor.update(cx, |editor, cx| {
24784 editor.stage_or_unstage_diff_hunks(
24785 true,
24786 vec![hunk_range.start..hunk_range.start],
24787 cx,
24788 );
24789 });
24790 }
24791 })
24792 } else {
24793 Button::new(("unstage", row as u64), "Unstage")
24794 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24795 .tooltip({
24796 let focus_handle = editor.focus_handle(cx);
24797 move |_window, cx| {
24798 Tooltip::for_action_in(
24799 "Unstage Hunk",
24800 &::git::ToggleStaged,
24801 &focus_handle,
24802 cx,
24803 )
24804 }
24805 })
24806 .on_click({
24807 let editor = editor.clone();
24808 move |_event, _window, cx| {
24809 editor.update(cx, |editor, cx| {
24810 editor.stage_or_unstage_diff_hunks(
24811 false,
24812 vec![hunk_range.start..hunk_range.start],
24813 cx,
24814 );
24815 });
24816 }
24817 })
24818 })
24819 .child(
24820 Button::new(("restore", row as u64), "Restore")
24821 .tooltip({
24822 let focus_handle = editor.focus_handle(cx);
24823 move |_window, cx| {
24824 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24825 }
24826 })
24827 .on_click({
24828 let editor = editor.clone();
24829 move |_event, window, cx| {
24830 editor.update(cx, |editor, cx| {
24831 let snapshot = editor.snapshot(window, cx);
24832 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24833 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24834 });
24835 }
24836 })
24837 .disabled(is_created_file),
24838 )
24839 .when(
24840 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24841 |el| {
24842 el.child(
24843 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24844 .shape(IconButtonShape::Square)
24845 .icon_size(IconSize::Small)
24846 // .disabled(!has_multiple_hunks)
24847 .tooltip({
24848 let focus_handle = editor.focus_handle(cx);
24849 move |_window, cx| {
24850 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24851 }
24852 })
24853 .on_click({
24854 let editor = editor.clone();
24855 move |_event, window, cx| {
24856 editor.update(cx, |editor, cx| {
24857 let snapshot = editor.snapshot(window, cx);
24858 let position =
24859 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24860 editor.go_to_hunk_before_or_after_position(
24861 &snapshot,
24862 position,
24863 Direction::Next,
24864 window,
24865 cx,
24866 );
24867 editor.expand_selected_diff_hunks(cx);
24868 });
24869 }
24870 }),
24871 )
24872 .child(
24873 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24874 .shape(IconButtonShape::Square)
24875 .icon_size(IconSize::Small)
24876 // .disabled(!has_multiple_hunks)
24877 .tooltip({
24878 let focus_handle = editor.focus_handle(cx);
24879 move |_window, cx| {
24880 Tooltip::for_action_in(
24881 "Previous Hunk",
24882 &GoToPreviousHunk,
24883 &focus_handle,
24884 cx,
24885 )
24886 }
24887 })
24888 .on_click({
24889 let editor = editor.clone();
24890 move |_event, window, cx| {
24891 editor.update(cx, |editor, cx| {
24892 let snapshot = editor.snapshot(window, cx);
24893 let point =
24894 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24895 editor.go_to_hunk_before_or_after_position(
24896 &snapshot,
24897 point,
24898 Direction::Prev,
24899 window,
24900 cx,
24901 );
24902 editor.expand_selected_diff_hunks(cx);
24903 });
24904 }
24905 }),
24906 )
24907 },
24908 )
24909 .into_any_element()
24910}
24911
24912pub fn multibuffer_context_lines(cx: &App) -> u32 {
24913 EditorSettings::try_get(cx)
24914 .map(|settings| settings.excerpt_context_lines)
24915 .unwrap_or(2)
24916 .min(32)
24917}